ZoneMinder-1.32.2/0000755000000000000000000000000013365454637012367 5ustar rootrootZoneMinder-1.32.2/COPYING0000644000000000000000000004325413365153155013421 0ustar rootroot 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. ZoneMinder-1.32.2/conf.d/0000755000000000000000000000000013365153155013525 5ustar rootrootZoneMinder-1.32.2/conf.d/README0000644000000000000000000000134413365153155014407 0ustar rootrootconf.d/README Any changes to ZoneMinder's configuration should be made here in this folder, rather than directly editing the default zm.conf file. ZoneMinder will process each file in this folder with a ".conf" extension. Each "Var = Value" pair, in each config file, will be loaded into ZoneMinder's running configuration, overriding any variables with the same name found in the default zm.conf file. After creating a custom config file, don't forget to set the proper file and owner permission on it. For example, this is typically what you should do after saving the config file to disk: sudo chown root:apache *.conf sudo chmod 640 *.conf Substitute "apache" with the name of the web server user account on your system. ZoneMinder-1.32.2/conf.d/01-system-paths.conf.in0000644000000000000000000000360713365153155017666 0ustar rootroot# ========================================================================== # # ZoneMinder System Paths Configuration # # ========================================================================== # # This config file contains the variables previously found under Options -> Paths # # *** DO NOT EDIT THIS FILE *** # # To make custom changes to the variables below, create a new configuration # file, with an extention of .conf, containing your desired modifications. # # Full path to the folder events are recorded to. # The web account user must have full read/write permission to this folder. ZM_DIR_EVENTS=@ZM_DIR_EVENTS@ # Full path to the folder images, not directly associated with events, # are recorded to. # The web account user must have full read/write permission to this folder. ZM_DIR_IMAGES=@ZM_DIR_IMAGES@ # Foldername under the webroot where ZoneMinder looks for optional sound files # to play when an alarm is detected. ZM_DIR_SOUNDS=@ZM_DIR_SOUNDS@ # Full path to the folder where exported archives are stored # The web account user must have full read/write permission to this folder. ZM_DIR_EXPORTS=@ZM_TMPDIR@ # ZoneMinder url path to the zms streaming server ZM_PATH_ZMS=@ZM_PATH_ZMS@ # Full Path to ZoneMinder's mapped memory files # The web account user must have full read/write permission to this folder. ZM_PATH_MAP=@ZM_PATH_MAP@ # Full Path to ZoneMinder's socket folder # The web account user must have full read/write permission to this folder. ZM_PATH_SOCKS=@ZM_SOCKDIR@ # Full path to ZoneMinder's log folder # The web account user must have full read/write permission to this folder. ZM_PATH_LOGS=@ZM_LOGDIR@ # Full path to ZoneMinder's swap folder # The web account user must have full read/write permission to this folder. ZM_PATH_SWAP=@ZM_TMPDIR@ # Full path to optional arp binary # ZoneMinder will find the arp binary automatically on most systems ZM_PATH_ARP="@ZM_PATH_ARP@" ZoneMinder-1.32.2/conf.d/02-multiserver.conf0000644000000000000000000000061313365153155017174 0ustar rootroot# ========================================================================== # # ZoneMinder Multiserver Configuration # # ========================================================================== # Do NOT set ZM_SERVER_HOST if you are not using Multi-Server # You have been warned # # The name specified here must have a corresponding entry # in the Servers tab under Options #ZM_SERVER_HOST= ZoneMinder-1.32.2/AUTHORS0000644000000000000000000000072713365153155013434 0ustar rootrootZoneMinder - A Linux based camera monitoring and analysis tool. This project was imagined and created by Philip Coombes in September 2002, shortly after being burglarised of his power tools. He can be contacted at philip.coombes@zoneminder.com In early 2013 after nearly two years of no development, the community reached out to Phil in an attempt to open up the project. With Phil's blessing, the code was migrated to github, and the community took over the project. ZoneMinder-1.32.2/scripts/0000755000000000000000000000000013365153155014045 5ustar rootrootZoneMinder-1.32.2/scripts/zmcontrol.pl.in0000644000000000000000000001403713365153155017043 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder Control Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== use strict; @EXTRA_PERL_LIB@ use ZoneMinder; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use POSIX qw/strftime EPIPE/; use Socket; #use Data::Dumper; use Module::Load::Conditional qw{can_load};; use constant MAX_CONNECT_DELAY => 15; use constant MAX_COMMAND_WAIT => 1800; $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); my $arg_string = join( " ", @ARGV ); my $id; my %options; GetOptions( 'id=i' =>\$id, 'command=s' =>\$options{command}, 'xcoord=i' =>\$options{xcoord}, 'ycoord=i' =>\$options{ycoord}, 'speed=i' =>\$options{speed}, 'step=i' =>\$options{step}, 'panspeed=i' =>\$options{panspeed}, 'tiltspeed=i' =>\$options{tiltspeed}, 'panstep=i' =>\$options{panstep}, 'tiltstep=i' =>\$options{tiltstep}, 'preset=i' =>\$options{preset}, 'autostop' =>\$options{autostop}, ) or pod2usage(-exitstatus => -1); if ( !$id || !$options{command} ) { print( STDERR "Please give a valid monitor id and command\n" ); pod2usage(-exitstatus => -1); } ( $id ) = $id =~ /^(\w+)$/; Debug("zmcontrol: arg string: $arg_string"); my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); my $saddr = sockaddr_un($sock_file); my $server_up = connect(CLIENT, $saddr); if ( !$server_up ) { # The server isn't there my $monitor = zmDbGetMonitorAndControl($id); if ( !$monitor ) { Fatal("Unable to load control data for monitor $id"); } my $protocol = $monitor->{Protocol}; if ( -x $protocol ) { # Protocol is actually a script! # Holdover from previous versions my $command .= $protocol.' '.$arg_string; Debug($command); my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { chomp($output); Debug("Output: $output"); } if ( $status ) { Error("Command '$command' exited with status: $status"); exit($status); } exit(0); } Info("Starting control server $id/$protocol"); close(CLIENT); if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) { Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); } if ( my $cpid = fork() ) { logReinit(); # Parent process just sleep and fall through socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or die("Can't open socket: $!"); my $attempts = 0; while ( !connect(CLIENT, $saddr) ) { $attempts++; Fatal("Can't connect: $! after $attempts attempts to $sock_file") if $attempts > MAX_CONNECT_DELAY; sleep(1); } } elsif ( defined($cpid) ) { close(STDOUT); close(STDERR); setpgrp(); logReinit(); Info("Control server $id/$protocol starting at " .strftime('%y/%m/%d %H:%M:%S', localtime()) ); $0 = $0." --id $id"; my $control = "ZoneMinder::Control::$protocol"->new($id); my $control_key = $control->getKey(); $control->loadMonitor(); $control->open(); socket(SERVER, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); unlink($sock_file); bind(SERVER, $saddr) or Fatal("Can't bind: $!"); listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); my $rin = ''; vec( $rin, fileno(SERVER), 1 ) = 1; my $win = $rin; my $ein = $win; my $timeout = MAX_COMMAND_WAIT; while( 1 ) { my $nfound = select(my $rout = $rin, undef, undef, $timeout); if ( $nfound > 0 ) { if ( vec( $rout, fileno(SERVER), 1 ) ) { my $paddr = accept(CLIENT, SERVER); my $message = ; next if !$message; my $params = jsonDecode($message); #Debug( Dumper( $params ) ); my $command = $params->{command}; close( CLIENT ); if ( $command eq 'quit' ) { last; } $control->$command($params); } else { Fatal('Bogus descriptor'); } } elsif ( $nfound < 0 ) { if ( $! == EPIPE ) { Error("Can't select: $!"); } else { Fatal("Can't select: $!"); } } else { #print( "Select timed out\n" ); last; } } # end while forever Info("Control server $id/$protocol exiting"); unlink($sock_file); $control->close(); exit(0); } else { Fatal("Can't fork: $!"); } } # end if !server up # The server is there, connect to it #print( "Writing commands\n" ); CLIENT->autoflush(); my $message = jsonEncode(\%options); print(CLIENT $message); shutdown(CLIENT, 1); exit(0); 1; __END__ =head1 NAME zmcontrol.pl - ZoneMinder control script =head1 SYNOPSIS zmcontrol.pl --id {monitor_id} --command={command} [various options] =head1 DESCRIPTION FIXME FIXME =head1 OPTIONS --autostop - --xcoord [ arg ] - X-coord --ycoord [ arg ] - Y-coord --speed [ arg ] - Speed --step [ arg ] - --panspeed [ arg ] - --panstep [ arg ] - --tiltspeed [ arg ] - --tiltstep [ arg ] - --preset [ arg ] - =cut ZoneMinder-1.32.2/scripts/zmsystemctl.pl.in0000644000000000000000000000410013365153155017400 0ustar rootroot#!/usr/bin/pkexec /usr/bin/perl # # ========================================================================== # # ZoneMinder systemctl wrapper, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmsystemctl.pl - ZoneMinder systemctl wrapper =head1 SYNOPSIS zmsystemctl.pl {start|stop|restart|version} =head1 DESCRIPTION This script allows an unpriveledged user to start, stop, or restart the zoneminder service on a system running systemd. It does this by redirecting commands through pkexec, which checks the available polkit policy files. The default policy file grants the system web account user permission. This can be changed or expanded by modifying the policy file. See man polkit for details. =head1 SEE ALSO polkit(8), pkexec(1) =cut use warnings; use strict; use bytes; use autouse 'Pod::Usage'=>qw(pod2usage); @EXTRA_PERL_LIB@ use ZoneMinder; my $command = $ARGV[0]; if ( (scalar(@ARGV) == 1) && ($command =~ /^(start|stop|restart|version)$/ ) ){ $command = $1; } else { pod2usage(-exitstatus => -1); } my $path = qx(which systemctl); chomp($path); my $status = $? >> 8; if ( !$path || $status ) { Fatal( "Unable to determine systemctl executable. Is systemd in use?" ); } Info( "Redirecting command through systemctl\n" ); exec("$path $command zoneminder"); ZoneMinder-1.32.2/scripts/zmaudit.pl.in0000644000000000000000000011216013365153155016465 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder Audit Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== use strict; use bytes; # ========================================================================== # # These are the elements you can edit to suit your installation # # ========================================================================== use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered use constant RECOVER_TEXT => 'Recovered.'; # Text to append to event notes when recovered # ========================================================================== # # You shouldn't need to change anything from here downwards # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use DBI; use POSIX; use File::Find; use Time::HiRes qw/gettimeofday/; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use constant IMAGE_PATH => $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_IMAGES}; use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) ? $Config{ZM_DIR_EVENTS} : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) ; use constant ZM_AUDIT_PID => '@ZM_RUNDIR@/zmaudit.pid'; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $report = 0; my $interactive = 0; my $continuous = 0; my $level = 1; my $monitor_id = 0; my $version; my $force = 0; my $server_id = undef; my $storage_id = undef; logInit(); GetOptions( continuous =>\$continuous, force =>\$force, interactive =>\$interactive, level =>\$level, 'monitor_id=i' =>\$monitor_id, report =>\$report, 'server_id=i' =>\$server_id, 'storage_id=i' =>\$storage_id, version =>\$version ) or pod2usage(-exitstatus => -1); if ( $version ) { print( ZoneMinder::Base::ZM_VERSION . "\n"); exit(0); } if ( ($report + $interactive + $continuous) > 1 ) { print( STDERR "Error, only one option may be specified\n" ); pod2usage(-exitstatus => -1); } if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { Fatal('ZM_AUDIT_MIN_AGE is not set in config.'); } if ( -e ZM_AUDIT_PID ) { local $/ = undef; open FILE, ZM_AUDIT_PID or die "Couldn't open file: $!"; binmode FILE; my $pid = ; close FILE; if ( $force ) { Error("zmaudit.pl appears to already be running at pid $pid. Continuing." ); } else { Fatal("zmaudit.pl appears to already be running at pid $pid. If not, please delete " . ZM_AUDIT_PID . " or use the --force command line option." ); } } # end if ZM_AUDIT_PID exists if ( open( my $PID, '>', ZM_AUDIT_PID ) ) { print( $PID $$ ); close( $PID ); } else { Error( "Can't open pid file at " . ZM_PID ); } sub HupHandler { Info("Received HUP, reloading"); &ZoneMinder::Logger::logHupHandler(); } sub TermHandler { Info("Received TERM, exiting"); Term(); } sub Term { unlink ZM_AUDIT_PID; exit( 0 ); } $SIG{HUP} = \&HupHandler; $SIG{TERM} = \&TermHandler; $SIG{INT} = \&TermHandler; my $dbh = zmDbConnect(); $| = 1; require ZoneMinder::Monitor; require ZoneMinder::Storage; require ZoneMinder::Event; my $max_image_age = 6/24; # 6 hours my $max_swap_age = 24/24; # 24 hours my $image_path = IMAGE_PATH; my $loop = 1; my $cleaned = 0; MAIN: while( $loop ) { if ( $continuous ) { # if we are running continuously, then just skip to the next # interval, otherwise we are a one off run, so wait a second and # retry until someone kills us. sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); } else { sleep 1; } # end if # After a long sleep, we may need to reconnect to the db while ( ! ( $dbh and $dbh->ping() ) ) { $dbh = zmDbConnect(); if ( ! $dbh ) { Error('Unable to connect to database'); if ( $continuous ) { # if we are running continuously, then just skip to the next # interval, otherwise we are a one off run, so wait a second and # retry until someone kills us. sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); } else { Term(); } # end if } # end if } # end while can't connect to the db my @Storage_Areas; if ( defined $storage_id ) { @Storage_Areas = ZoneMinder::Storage->find( Id=>$storage_id ); if ( !@Storage_Areas ) { Error("No Storage Area found with Id $storage_id"); Term(); } Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); } elsif ( $server_id ) { @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id ); if ( ! @Storage_Areas ) { Error("No Storage Area found with ServerId =" . $server_id); Term(); } foreach my $Storage ( @Storage_Areas ) { Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); } } else { @Storage_Areas = ZoneMinder::Storage->find(); Info("Auditing All Storage Areas"); } my %Monitors; my $db_monitors; my $monitorSelectSql = $monitor_id ? 'SELECT * FROM Monitors WHERE Id=?' : 'SELECT * FROM Monitors ORDER BY Id'; my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) AS Age FROM Events WHERE MonitorId = ?'.(@Storage_Areas ? ' AND StorageId IN ('.join(',',map { '?'} @Storage_Areas).')' : '' ). ' ORDER BY Id'; my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); $cleaned = 0; my $res = $monitorSelectSth->execute( $monitor_id ? $monitor_id : () ) or Fatal( "Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr() ); while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { $Monitors{$$monitor{Id}} = $monitor; my $db_events = $db_monitors->{$monitor->{Id}} = {}; my $res = $eventSelectSth->execute( $monitor->{Id}, map { $$_{Id} } @Storage_Areas ) or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); while ( my $event = $eventSelectSth->fetchrow_hashref() ) { $db_events->{$event->{Id}} = $event->{Age}; } Debug( 'Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}" ); } # end while monitors my $fs_monitors; foreach my $Storage ( @Storage_Areas ) { Debug('Checking events in ' . $Storage->Path() ); if ( ! chdir( $Storage->Path() ) ) { Error( 'Unable to change dir to ' . $Storage->Path() ); next; } # end if # Please note that this glob will take all files beginning with a digit. foreach my $monitor ( glob('[0-9]*') ) { if ( $monitor =~ /\D/ ) { Debug("Weird non digit characters in $monitor"); next; } if ( $monitor_id and ( $monitor_id != $monitor ) ) { Debug("Skipping monitor $monitor because we are only interested in monitor $monitor_id"); next; } Debug( "Found filesystem monitor '$monitor'" ); $fs_monitors->{$monitor} = {} if ! $fs_monitors->{$monitor}; my $fs_events = $fs_monitors->{$monitor}; # De-taint ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); { my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' events'); foreach my $day_dir ( @day_dirs ) { Debug( "Checking day dir $day_dir" ); ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint if ( !chdir($day_dir) ) { Error("Can't chdir to '$$Storage{Path}/$day_dir': $!"); next; } if ( ! opendir(DIR, '.') ) { Error("Can't open directory '$$Storage{Path}/$day_dir': $!"); next; } my %event_ids_by_path; my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); Debug("Have " . @event_links . ' event links'); closedir(DIR); my $count = 0; foreach my $event_link ( @event_links ) { # Event links start with a period and consist of the digits of the event id. Anything else is not an event link my ($event_id) = $event_link =~ /^\.(\d+)$/; if ( !$event_id ) { Warning("Non-event link found $event_link in $day_dir, skipping"); next; } Debug("Checking link $event_link"); #Event path is hour/minute/sec my $event_path = readlink($event_link); if ( !($event_path and -e $event_path) ) { aud_print("Event link $day_dir/$event_link does not point to valid target at $event_path"); if ( confirm() ) { ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint unlink($event_link); $cleaned = 1; } } else { $event_ids_by_path{$event_path} = $event_id; Debug("Checking link $event_link points to $event_path "); my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); $$Event{Id} = $event_id; $$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_path); $$Event{RelativePath} = join('/', $day_dir, $event_path); $$Event{Scheme} = 'Deep'; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); $Event->DiskSpace( undef ); } # event path exists } # end foreach event_link # Now check for events that have lost their link my @time_dirs = glob('[0-9][0-9]/[0-9][0-9]/[0-9][0-9]'); foreach my $event_dir ( @time_dirs ) { Debug("Checking time dir $event_dir"); ( $event_dir ) = ( $event_dir =~ /^(.*)$/ ); # De-taint my $event_id = undef; if ( ! opendir(DIR, $event_dir) ) { Error("Can't open directory '$$Storage{Path}/$day_dir': $!"); next; } my @contents = readdir( DIR ); Debug("Have " . @contents . " files in $day_dir/$event_dir"); closedir(DIR); my @mp4_files = grep( /^\d+\-video.mp4$/, @contents); foreach my $mp4_file ( @mp4_files ) { my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/; if ( $id ) { $event_id = $id; Debug("Got event id from mp4 file $mp4_file => $event_id"); last; } } if ( ! $event_id ) { # Look for .id file my @hidden_files = grep( /^\.\d+$/, @contents); Debug("Have " . @hidden_files . ' hidden files'); if ( @hidden_files ) { ( $event_id ) = $hidden_files[0] =~ /^.(\d+)$/; } } if ( $event_id and ! $fs_events->{$event_id} ) { my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); $$Event{Id} = $event_id; $$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir); $$Event{RelativePath} = join('/', $day_dir, $event_dir); $$Event{Scheme} = 'Deep'; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); $Event->DiskSpace( undef ); if ( ! $event_ids_by_path{$event_dir} ) { Warning("No event link found at ".$Event->LinkPath() ." for " . $Event->to_string()); } } else { if ( $event_ids_by_path{$event_dir} ) { Debug("Have an event link, leaving dir alone."); next; } my ( undef, $year, $month, $day ) = split('/', $day_dir); $year += 2000; my ( $hour, $minute, $second ) = split('/', $event_dir); my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); my $Event = ZoneMinder::Event->find_one( MonitorId=>$monitor_dir, StartTime=>$StartTime, ); if ( $Event ) { Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string()); next; } aud_print("Deleting event directories with no event id information at $day_dir/$event_dir"); if ( confirm() ) { my $command = "rm -rf $event_dir"; executeShellCommand( $command ); $cleaned = 1; } } # end if able to find id } # end foreach event_dir without link chdir( $Storage->Path() ); } # end foreach day dir } Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . " entries." ); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { Debug( "$event_dir is not a dir. Skipping" ); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; if ( ! $event_id ) { Debug("Unable to parse date/event_id from $event_dir"); next; } my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); $$Event{Id} = $event_id; $$Event{Path} = join('/', $Storage->Path(), $event_dir ); Debug("Have event $$Event{Id} at $$Event{Path}"); $$Event{Scheme} = 'Medium'; $$Event{RelativePath} = $event_dir; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); } # end foreach event } if ( ! $$Storage{Scheme} ) { Error("Storage Scheme not set on $$Storage{Name}"); if ( ! chdir( $monitor_dir ) ) { Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); next; } if ( ! opendir( DIR, "." ) ) { Error( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ); next; } my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); closedir( DIR ); my $count = 0; foreach my $event ( @temp_events ) { my $Event = $fs_events->{$event} = new ZoneMinder::Event(); $$Event{Id} = $event; #$$Event{Path} = $event_path; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir\n" ); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor if ( $cleaned ) { Debug("First stage cleaning done. Restarting."); redo MAIN; } $cleaned = 0; while ( my ( $monitor_id, $fs_events ) = each(%$fs_monitors) ) { if ( my $db_events = $db_monitors->{$monitor_id} ) { if ( ! $fs_events ) { Debug("No fs_events for database monitor $monitor_id"); next; } my @event_ids = keys %$fs_events; Debug("Have " .scalar @event_ids . " events for monitor $monitor_id"); foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) { my $Event = $fs_events->{$fs_event_id}; if ( ! defined( $db_events->{$fs_event_id} ) ) { my $age = $Event->age(); if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; delete $fs_events->{$fs_event_id}; } # end if confirm } # end if old enough } # end if ! in db events } # end foreach fs event } else { aud_print( "Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database" ); if ( confirm() ) { my $command = "rm -rf $monitor_id"; executeShellCommand( $command ); $cleaned = 1; } } } # end foreach monitor/filesystem events my $monitor_links; foreach my $link ( glob('*') ) { next if ( !-l $link ); next if ( -e $link ); aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); if ( confirm() ) { ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint my $command = qq`rm "$link"`; executeShellCommand( $command ); $cleaned = 1; } } } # end foreach Storage Area redo MAIN if ( $cleaned ); $cleaned = 0; my $deleteMonitorSql = 'DELETE LOW_PRIORITY FROM Monitors WHERE Id = ?'; my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); my $deleteEventSql = 'DELETE LOW_PRIORITY FROM Events WHERE Id = ?'; my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); my $deleteFramesSql = 'DELETE LOW_PRIORITY FROM Frames WHERE EventId = ?'; my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); my $deleteStatsSql = 'DELETE LOW_PRIORITY FROM Stats WHERE EventId = ?'; my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); # Foreach database monitor and it's list of events. while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) { # If we found the monitor in the file system if ( my $fs_events = $fs_monitors->{$db_monitor} ) { next if ! $db_events; while ( my ( $db_event, $age ) = each( %$db_events ) ) { if ( ! defined( $fs_events->{$db_event} ) ) { my $Event = ZoneMinder::Event->find_one( Id=>$db_event ); if ( ! $Event ) { Debug("Event $db_event is no longer in db. Filter probably deleted it while we were auditing."); next; } Debug("Event $db_event is not in fs. Should have been at ".$Event->Path()); if ( $Event->Archived() ) { Warning("Event $$Event{Id} is Archived. Taking no further action on it."); next; } if ( ! $Event->StartTime() ) { Info("Event $$Event{Id} has no start time. deleting it."); if ( confirm() ) { $Event->delete(); $cleaned = 1; } next; } if ( ! $Event->EndTime() ) { if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { Info("Event $$Event{Id} has no end time and is $age seconds old. deleting it."); if ( confirm() ) { $Event->delete(); $cleaned = 1; } next; } } if ( $Event->check_for_in_filesystem() ) { Debug("Database event $$Event{Id} apparently exists at " . $Event->Path() ); } else { if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { aud_print( "Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem, deleting' ); if ( confirm() ) { $Event->delete(); $cleaned = 1; } } else { aud_print( "Database event '".$Event->Path()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" ); } } # end if exists in filesystem } # end if ! in fs_events } # foreach db_event #} else { #my $Monitor = new ZoneMinder::Monitor( $db_monitor ); #my $Storage = $Monitor->Storage(); #aud_print( "Database monitor '$db_monitor' does not exist in filesystem, should have been at ".$Storage->Path().'/'.$Monitor->Id()."\n" ); #if ( confirm() ) #{ # We don't actually do this in case it's new #my $res = $deleteMonitorSth->execute( $db_monitor ) # or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() ); #$cleaned = 1; #} } } # end foreach db_monitor if ( $cleaned ) { Debug("Have done some cleaning, restarting."); redo MAIN; } if ( $level > 1 ) { # Remove orphaned events (with no monitor) # Shouldn't be possible anymore with FOREIGN KEYS in place $cleaned = 0; Debug("Checking for Orphaned Events"); my $selectOrphanedEventsSql = 'SELECT Events.Id, Events.Name FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) WHERE isnull(Monitors.Id)'; my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) or Error( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); $res = $selectOrphanedEventsSth->execute() or Error( "Can't execute: ".$selectOrphanedEventsSth->errstr() ); while( my $event = $selectOrphanedEventsSth->fetchrow_hashref() ) { aud_print( "Found orphaned event with no monitor '$event->{Id}'" ); if ( confirm() ) { if ( $res = $deleteEventSth->execute( $event->{Id} ) ) { $cleaned = 1; } else { Error( "Can't execute: ".$deleteEventSth->errstr() ); } } } redo MAIN if $cleaned; } # end if level > 1 # Remove empty events (with no frames) $cleaned = 0; Debug("Checking for Events with no Frames"); my $selectEmptyEventsSql = 'SELECT E.Id AS Id, E.StartTime, F.EventId FROM Events as E LEFT JOIN Frames as F ON (E.Id = F.EventId) WHERE isnull(F.EventId) AND now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second > E.StartTime'; if ( my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) ) { if ( $res = $selectEmptyEventsSth->execute() ) { while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) { aud_print( "Found empty event with no frame records '$event->{Id}' at $$event{StartTime}" ); if ( confirm() ) { if ( $res = $deleteEventSth->execute( $event->{Id} ) ) { $cleaned = 1; } else { Error( "Can't execute: ".$deleteEventSth->errstr() ); } } } # end foreach row } else { Error( "Can't execute: ".$selectEmptyEventsSth->errstr() ); } } else { Error( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() ); } redo MAIN if $cleaned; # Remove orphaned frame records $cleaned = 0; Debug("Checking for Orphaned Frames"); my $selectOrphanedFramesSql = 'SELECT DISTINCT EventId FROM Frames WHERE (SELECT COUNT(*) FROM Events WHERE Events.Id=EventId)=0'; my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); $res = $selectOrphanedFramesSth->execute() or Fatal( "Can't execute: ".$selectOrphanedFramesSth->errstr() ); while( my $frame = $selectOrphanedFramesSth->fetchrow_hashref() ) { aud_print( "Found orphaned frame records for event '$frame->{EventId}'" ); if ( confirm() ) { $res = $deleteFramesSth->execute( $frame->{EventId} ) or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); $cleaned = 1; } } redo MAIN if $cleaned; if ( $level > 1 ) { # Remove orphaned stats records $cleaned = 0; Debug("Checking for Orphaned Stats"); my $selectOrphanedStatsSql = 'SELECT DISTINCT EventId FROM Stats WHERE EventId NOT IN (SELECT Id FROM Events)'; my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); $res = $selectOrphanedStatsSth->execute() or Fatal( "Can't execute: ".$selectOrphanedStatsSth->errstr() ); while( my $stat = $selectOrphanedStatsSth->fetchrow_hashref() ) { aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" ); if ( confirm() ) { $res = $deleteStatsSth->execute( $stat->{EventId} ) or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); $cleaned = 1; } } redo MAIN if ( $cleaned ); } # New audit to close any events that were left open for longer than MIN_AGE seconds my $selectUnclosedEventsSql = #"SELECT E.Id, ANY_VALUE(E.MonitorId), # #max(F.TimeStamp) as EndTime, #unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, #max(F.FrameId) as Frames, #count(if(F.Score>0,1,NULL)) as AlarmFrames, #sum(F.Score) as TotScore, #max(F.Score) as MaxScore #FROM Events as E #INNER JOIN Frames as F on E.Id = F.EventId #WHERE isnull(E.Frames) or isnull(E.EndTime) #GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}.' second)' #; 'SELECT *, unix_timestamp(StartTime) AS TimeStamp FROM Events WHERE EndTime IS NULL AND StartTime < (now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second)'; my $selectFrameDataSql = ' SELECT max(TimeStamp) as EndTime, unix_timestamp(max(TimeStamp)) AS EndTimeStamp, max(FrameId) as Frames, count(if(Score>0,1,NULL)) as AlarmFrames, sum(Score) as TotScore, max(Score) as MaxScore FROM Frames WHERE EventId=?'; my $selectFrameDataSth = $dbh->prepare_cached($selectFrameDataSql) or Fatal( "Can't prepare '$selectFrameDataSql': ".$dbh->errstr() ); my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); my $updateUnclosedEventsSql = "UPDATE low_priority Events SET Name = ?, EndTime = ?, Length = ?, Frames = ?, AlarmFrames = ?, TotScore = ?, AvgScore = ?, MaxScore = ?, Notes = concat_ws( ' ', Notes, ? ) WHERE Id = ?" ; my $updateUnclosedEventsSth = $dbh->prepare_cached( $updateUnclosedEventsSql ) or Fatal( "Can't prepare '$updateUnclosedEventsSql': ".$dbh->errstr() ); $res = $selectUnclosedEventsSth->execute() or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) { aud_print( "Found open event '$event->{Id}' at $$event{StartTime}" ); if ( confirm( 'close', 'closing' ) ) { if ( ! ( $res = $selectFrameDataSth->execute($event->{Id}) ) ) { Error( "Can't execute: $selectFrameDataSql:".$selectFrameDataSth->errstr() ); next; } my $frame = $selectFrameDataSth->fetchrow_hashref(); if ( $frame ) { $res = $updateUnclosedEventsSth->execute( sprintf('%s%d%s', $Monitors{$event->{MonitorId}}->{EventPrefix}, $event->{Id}, RECOVER_TAG ), $frame->{EndTime}, $frame->{EndTimeStamp} - $event->{TimeStamp}, $frame->{Frames}, $frame->{AlarmFrames}, $frame->{TotScore}, $frame->{AlarmFrames} ? int($frame->{TotScore} / $frame->{AlarmFrames}) : 0 , $frame->{MaxScore}, RECOVER_TEXT, $event->{Id} ) or Error( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); } else { Error('SHOULD DELETE'); } # end if has frame data } } # end while unclosed event Debug("Done closing open events."); # Now delete any old image files if ( my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}> ) { aud_print( 'Deleting '.int(@old_files)." old images\n" ); my $untainted_old_files = join( ';', @old_files ); ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); unlink( split( /;/, $untainted_old_files ) ); } # Now delete any old swap files ( my $swap_image_root ) = ( $Config{ZM_PATH_SWAP} =~ /^(.*)$/ ); # De-taint File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root ); # Prune the Logs table if required if ( $Config{ZM_LOG_DATABASE_LIMIT} ) { if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) { # Number of rows my $selectLogRowCountSql = 'SELECT count(*) AS Rows FROM Logs'; my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); $res = $selectLogRowCountSth->execute() or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() ); my $row = $selectLogRowCountSth->fetchrow_hashref(); my $logRows = $row->{Rows}; if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) { my $deleteLogByRowsSql = 'DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?'; my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); if ( $deleteLogByRowsSth->rows() ) { aud_print( 'Deleted '.$deleteLogByRowsSth->rows() ." log table entries by count\n" ); } } } else { # Time of record # 7 days is invalid. We need to remove the s if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) { $Config{ZM_LOG_DATABASE_LIMIT} = $1; } my $deleted_rows; do { my $deleteLogByTimeSql = 'DELETE FROM Logs WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 10'; my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); $res = $deleteLogByTimeSth->execute() or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); $deleted_rows = $deleteLogByTimeSth->rows(); aud_print( "Deleted $deleted_rows log table entries by time\n" ); } while ( $deleted_rows ); } } # end if ZM_LOG_DATABASE_LIMIT $loop = $continuous; my $eventcounts_sql = q` UPDATE Monitors SET TotalEvents=(SELECT COUNT(Id) FROM Events WHERE MonitorId=Monitors.Id), TotalEventDiskSpace=(SELECT SUM(DiskSpace) FROM Events WHERE MonitorId=Monitors.Id AND DiskSpace IS NOT NULL), ArchivedEvents=(SELECT COUNT(Id) FROM Events WHERE MonitorId=Monitors.Id AND Archived=1), ArchivedEventDiskSpace=(SELECT SUM(DiskSpace) FROM Events WHERE MonitorId=Monitors.Id AND Archived=1 AND DiskSpace IS NOT NULL) `; my $eventcounts_sth = $dbh->prepare_cached( $eventcounts_sql ); $eventcounts_sth->execute(); $eventcounts_sth->finish(); my $eventcounts_hour_sql = q` UPDATE Monitors INNER JOIN ( SELECT MonitorId, COUNT(*) AS HourEvents, SUM(COALESCE(DiskSpace,0)) AS HourEventDiskSpace FROM Events_Hour GROUP BY MonitorId ) AS E ON E.MonitorId=Monitors.Id SET Monitors.HourEvents = E.HourEvents, Monitors.HourEventDiskSpace = E.HourEventDiskSpace `; my $eventcounts_day_sql = q` UPDATE Monitors INNER JOIN ( SELECT MonitorId, COUNT(*) AS DayEvents, SUM(COALESCE(DiskSpace,0)) AS DayEventDiskSpace FROM Events_Day GROUP BY MonitorId ) AS E ON E.MonitorId=Monitors.Id SET Monitors.DayEvents = E.DayEvents, Monitors.DayEventDiskSpace = E.DayEventDiskSpace `; my $eventcounts_week_sql = q` UPDATE Monitors INNER JOIN ( SELECT MonitorId, COUNT(*) AS WeekEvents, SUM(COALESCE(DiskSpace,0)) AS WeekEventDiskSpace FROM Events_Week GROUP BY MonitorId ) AS E ON E.MonitorId=Monitors.Id SET Monitors.WeekEvents = E.WeekEvents, Monitors.WeekEventDiskSpace = E.WeekEventDiskSpace `; my $eventcounts_month_sql = q` UPDATE Monitors INNER JOIN ( SELECT MonitorId, COUNT(*) AS MonthEvents, SUM(COALESCE(DiskSpace,0)) AS MonthEventDiskSpace FROM Events_Month GROUP BY MonitorId ) AS E ON E.MonitorId=Monitors.Id SET Monitors.MonthEvents = E.MonthEvents, Monitors.MonthEventDiskSpace = E.MonthEventDiskSpace `; my $eventcounts_hour_sth = $dbh->prepare_cached( $eventcounts_hour_sql ); my $eventcounts_day_sth = $dbh->prepare_cached( $eventcounts_day_sql ); my $eventcounts_week_sth = $dbh->prepare_cached( $eventcounts_week_sql ); my $eventcounts_month_sth = $dbh->prepare_cached( $eventcounts_month_sql ); $eventcounts_hour_sth->execute( ) or Error( "Can't execute: ".$eventcounts_sth->errstr() ); $eventcounts_day_sth->execute( ) or Error( "Can't execute: ".$eventcounts_sth->errstr() ); $eventcounts_week_sth->execute( ) or Error( "Can't execute: ".$eventcounts_sth->errstr() ); $eventcounts_month_sth->execute( ) or Error( "Can't execute: ".$eventcounts_sth->errstr() ); sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ) if $continuous; }; Term(); sub aud_print { my $string = shift; if ( ! $continuous ) { print( $string ); } else { Info( $string ); } } sub confirm { my $prompt = shift || 'delete'; my $action = shift || 'deleting'; my $yesno = 0; if ( $report ) { print( "\n" ); } elsif ( $interactive ) { print( ", $prompt Y/n/q: " ); my $char = <>; chomp( $char ); if ( $char eq 'q' ) { exit( 0 ); } if ( !$char ) { $char = 'y'; } $yesno = ( $char =~ /[yY]/ ); } else { if ( !$continuous ) { print( ", $action\n" ); } else { Info( $action ); } $yesno = 1; } return( $yesno ); } sub deleteSwapImage { my $file = $_; return if $file =~ /^./; if ( $file !~ /^zmswap-/ ) { Error( "Trying to delete SwapImage that isnt a swap image $file" ); return; } # Ignore directories if ( -d $file ) { Error( "Trying to delete a directory instead of a swap image $file" ); return; } if ( -M $file > $max_swap_age ) { ( $file ) = ( $file =~ /^(.*)$/ ); Debug( "Deleting $file" ); unlink( $file ); } } # Deletes empty sub directories of the given path. # Does not delete the path if empty. Is not meant to be recursive. sub delete_empty_subdirs { my $DIR; if ( !opendir($DIR, $_[0]) ) { Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" ); return; } my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR ); Debug("delete_empty_subdirectories $_[0] has " . @contents .' entries:' . ( @contents < 2 ? join(',',@contents) : '' )); my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents; Debug("Have " . @dirs . " dirs"); foreach ( @dirs ) { delete_empty_directories( $_[0].'/'.$_ ); } closedir($DIR); } sub delete_empty_directories { my $DIR; if ( !opendir($DIR, $_[0]) ) { Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" ); return; } my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR ); Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' )); my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents; if ( @dirs ) { Debug("Have " . @dirs . " dirs"); foreach ( @dirs ) { delete_empty_directories( $_[0].'/'.$_ ); } #Reload, since we may now be empty rewinddir $DIR; @contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir( $DIR ); } closedir($DIR); if ( ! @contents ) { ( my $dir ) = ( $_[0] =~ /^(.*)$/ ); Debug("Unlinking $dir because it's empty"); if ( ! rmdir $dir ) { Error("Unable to unlink $dir: $!"); } } } # end sub delete_empty_directories 1; __END__ =head1 NAME zmaudit.pl - ZoneMinder event file system and database consistency checker =head1 SYNOPSIS zmaudit.pl [-r,-report|-i,-interactive] =head1 DESCRIPTION This script checks for consistency between the event filesystem and the database. If events are found in one and not the other they are deleted (optionally). Additionally any monitor event directories that do not correspond to a database monitor are similarly disposed of. However monitors in the database that don't have a directory are left alone as this is valid if they are newly created and have no events yet. =head1 OPTIONS -c, --continuous - Run continuously -f, --force - Run even if pid file exists -i, --interactive - Ask before applying any changes -m, --monitor_id - Only consider the given monitor -r, --report - Just report don't actually do anything -s, --storage_id - Specify a storage area to audit instead of all -v, --version - Print the installed version of ZoneMinder =cut ZoneMinder-1.32.2/scripts/zmdbbackup.in0000644000000000000000000000222413365153155016517 0ustar rootroot#!/bin/bash #=============================================================================== # # FILE: zmdbbackup # # USAGE: ./zmdbbackup # # DESCRIPTION: Uses mysqldump to backup the config info in the zm DB # OPTIONS: --- None # REQUIREMENTS: --- mysqldump # BUGS: --- # NOTES: --- # AUTHOR: Ross Melin # COMPANY: # VERSION: 2.0 # CREATED: 05/26/2006 06:21:00 AM PDT # REVISION: --- #=============================================================================== # Edit these to suit your configuration ZM_CONFIG=@ZM_CONFIG@ source $ZM_CONFIG # ZM_VERSION in the config is now deprecated but will likely still exist in people's config files. This will override it. ZM_VERSION=@VERSION@ MYSQLDUMP=/usr/bin/mysqldump BACKUP_PATH=/var/lib/zm BACKUP_FILE=zm_backup.sql DUMPOPTS="--user=$ZM_DB_USER --password=$ZM_DB_PASS --opt" TABLES="Config Filters Groups Monitors States TriggersX10 Users Zones" OUTFILE="$BACKUP_PATH/$BACKUP_FILE" echo "-- -- Created by zm_db_backup for ZoneMinder Version $ZM_VERSION --" > $OUTFILE $MYSQLDUMP $DUMPOPTS zm $TABLES >> $OUTFILE exit 0 ZoneMinder-1.32.2/scripts/zmtrack.pl.in0000644000000000000000000001343713365153155016472 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder Experimental PTZ Tracking Script, $Date: 2009-06-08 10:11:56 +0100 (Mon, 08 Jun 2009) $, $Revision: 2908 $ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmtrack.pl - ZoneMinder Experimental PTZ Tracking Script =head1 SYNOPSIS zmtrack.pl -m zmtrack.pl --monitor= =head1 OPTIONS -m, --monitor= - Id of the monitor to track =head1 DESCRIPTION This script is used to trigger and cancel alarms from external sources using an arbitrary text based format. =cut use strict; use bytes; # ========================================================================== # # User config # # ========================================================================== use constant SLEEP_TIME => 10000; # In microseconds # ========================================================================== # # Don't change anything from here on down # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use DBI; use POSIX; use autouse 'Data::Dumper'=>qw(Dumper); use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use Time::HiRes qw( usleep ); $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $mid = 0; GetOptions( 'monitor=s'=>\$mid ) or pod2usage(-exitstatus => -1); logInit(); logSetSignal(); my ( $detaint_mid ) = $mid =~ /^(\d+)$/; $mid = $detaint_mid; print( "Tracker daemon $mid (experimental) starting at " .strftime( '%y/%m/%d %H:%M:%S', localtime() ) ."\n" ); my $dbh = zmDbConnect(); my $sql = "SELECT C.*,M.* FROM Monitors as M LEFT JOIN Controls as C on M.ControlId = C.Id WHERE M.Id = ?" ; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $mid ) or Fatal( "Can't execute '$sql': ".$sth->errstr() ); my $monitor = $sth->fetchrow_hashref(); if ( !$monitor ) { print( "Can't find monitor '$mid'\n" ); exit( -1 ); } if ( !$monitor->{Controllable} ) { print( "Monitor '$mid' is not controllable\n" ); exit( -1 ); } if ( !$monitor->{TrackMotion} ) { print( "Monitor '$mid' is not configured to track motion\n" ); exit( -1 ); } if ( !$monitor->{CanMoveMap} ) { print( "Monitor '$mid' cannot move in map mode" ); if ( $monitor->{CanMoveRel} ) { print( ", falling back to pseudo map mode\n" ); } else { print( "\n" ); exit( -1 ); } } Debug( "Found monitor for id '$monitor'\n" ); exit( -1 ) if ( !zmMemVerify( $monitor ) ); sub Suspend { my $monitor = shift; zmMonitorSuspend( $monitor ); } sub Resume { my $monitor = shift; sleep( $monitor->{TrackDelay} ); zmMonitorResume( $monitor ); } sub Track { my $monitor = shift; my ( $x, $y ) = @_; my ( $detaint_x ) = $x =~ /^(\d+)$/; $x = $detaint_x; my ( $detaint_y ) = $y =~ /^(\d+)$/; $y = $detaint_y; my $ctrlCommand = $Config{ZM_PATH_BIN} ."/zmcontrol.pl -i " .$monitor->{Id} ; $ctrlCommand .= " --command=" .( $monitor->{CanMoveMap} ? "moveMap" : "movePseudoMap" ) ." --xcoord=$x --ycoord=$y" ; executeShellCommand( $ctrlCommand ); } sub Return { my $monitor = shift; my $ctrlCommand = $Config{ZM_PATH_BIN} ."/zmcontrol.pl -i " .$monitor->{Id} ; if ( $monitor->{ReturnLocation} > 0 ) { $ctrlCommand .= " --command=presetGoto --preset=" .$monitor->{ReturnLocation} ; } else { $ctrlCommand .= " --command=presetHome"; } executeShellCommand( $ctrlCommand ); } my $last_alarm = 0; if ( ($monitor->{ReturnLocation} >= 0) ) { Suspend( $monitor ); Return( $monitor ); Resume( $monitor ); } my $alarmed = undef; while( 1 ) { if ( zmIsAlarmed( $monitor ) ) { my ( $alarm_x, $alarm_y ) = zmGetAlarmLocation( $monitor ); if ( $alarm_x >= 0 && $alarm_y >= 0 ) { Debug( "Got alarm at $alarm_x, $alarm_y\n" ); Suspend( $monitor ); Track( $monitor, $alarm_x, $alarm_y ); Resume( $monitor ); $last_alarm = time(); $alarmed = !undef; } } else { if ( logDebugging() && $alarmed ) { print( "Left alarm state\n" ); $alarmed = undef; } if ( ($monitor->{ReturnLocation} >= 0) && ($last_alarm > 0) && ((time()-$last_alarm) > $monitor->{ReturnDelay}) ) { Debug( "Returning to location ".$monitor->{ReturnLocation}."\n" ); Suspend( $monitor ); Return( $monitor ); Resume( $monitor ); $last_alarm = 0; } } usleep( SLEEP_TIME ); } ZoneMinder-1.32.2/scripts/zmlogrotate.conf.in0000644000000000000000000000035013365153155017666 0ustar rootroot# First the log files /var/log/zm/*log { weekly rotate 3 notifempty missingok } # Now the weekly db backup /var/lib/zm/zm_backup.sql { weekly rotate 3 missingok compress postrotate @BINDIR@/zmdbbackup endscript } ZoneMinder-1.32.2/scripts/zmtrigger.pl.in0000644000000000000000000004154613365153155017033 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder External Trigger Script # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== use strict; use bytes; # ========================================================================== # # User config # # ========================================================================== use constant MAX_CONNECT_DELAY => 10; use constant MONITOR_RELOAD_INTERVAL => 300; use constant SELECT_TIMEOUT => 0.25; # ========================================================================== # # Channel/Connection Modules # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use ZoneMinder::Trigger::Channel::Inet; use ZoneMinder::Trigger::Channel::Unix; use ZoneMinder::Trigger::Channel::Serial; use ZoneMinder::Trigger::Connection; my @connections; push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan1 TCP on port 6802', channel=>ZoneMinder::Trigger::Channel::Inet->new( port=>6802 ), mode=>'rw' ) ); push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan2 Unix Socket at ' . $Config{ZM_PATH_SOCKS}.'/zmtrigger.sock', channel=>ZoneMinder::Trigger::Channel::Unix->new( path=>$Config{ZM_PATH_SOCKS}.'/zmtrigger.sock' ), mode=>'rw' ) ); #push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan3', channel=>ZoneMinder::Trigger::Channel::File->new( path=>'/tmp/zmtrigger.out' ), mode=>'w' ) ); #push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan4', channel=>ZoneMinder::Trigger::Channel::Serial->new( path=>'/dev/ttyS0' ), mode=>'rw' ) ); # ========================================================================== # # Don't change anything from here on down # # ========================================================================== use DBI; #use Socket; use autouse 'Data::Dumper'=>qw(Dumper); use POSIX qw( EINTR ); use Time::HiRes qw( usleep ); $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); Info( "Trigger daemon starting" ); my $dbh = zmDbConnect(); my $base_rin = ''; foreach my $connection ( @connections ) { Info( "Opening connection '$connection->{name}'" ); $connection->open(); } my @in_select_connections = grep { $_->input() && $_->selectable() } @connections; my @in_poll_connections = grep { $_->input() && !$_->selectable() } @connections; my @out_connections = grep { $_->output() } @connections; foreach my $connection ( @in_select_connections ) { vec( $base_rin, $connection->fileno(), 1 ) = 1; } my %spawned_connections; my %monitors; my $monitor_reload_time = 0; my @needsReload; loadMonitors(); $! = undef; my $rin = ''; my $win = $rin; my $ein = $win; my $timeout = SELECT_TIMEOUT; my %actions; while( 1 ) { $rin = $base_rin; # Add the file descriptors of any spawned connections foreach my $fileno ( keys(%spawned_connections) ) { vec( $rin, $fileno, 1 ) = 1; } my $nfound = select( my $rout = $rin, undef, my $eout = $ein, $timeout ); if ( $nfound > 0 ) { Debug( "Got input from $nfound connections" ); foreach my $connection ( @in_select_connections ) { if ( vec( $rout, $connection->fileno(), 1 ) ) { Debug( 'Got input from connection ' .$connection->name() .' (' .$connection->fileno() .")" ); if ( $connection->spawns() ) { my $new_connection = $connection->accept(); $spawned_connections{$new_connection->fileno()} = $new_connection; Debug( 'Added new spawned connection (' .$new_connection->fileno() .'), ' .int(keys(%spawned_connections)) ." spawned connections" ); } else { my $messages = $connection->getMessages(); if ( defined($messages) ) { foreach my $message ( @$messages ) { handleMessage( $connection, $message ); } } } } } # end foreach connection foreach my $connection ( values(%spawned_connections) ) { if ( vec( $rout, $connection->fileno(), 1 ) ) { Debug( 'Got input from spawned connection ' .$connection->name() .' (' .$connection->fileno() .")" ); my $messages = $connection->getMessages(); if ( defined($messages) ) { foreach my $message ( @$messages ) { handleMessage( $connection, $message ); } } else { delete( $spawned_connections{$connection->fileno()} ); Debug( 'Removed spawned connection (' .$connection->fileno() .'), ' .int(keys(%spawned_connections)) ." spawned connections" ); $connection->close(); } } } # end foreach spawned connection } elsif ( $nfound < 0 ) { if ( $! == EINTR ) { # Do nothing } else { Fatal( "Can't select: $!" ); } } # end if select returned activitiy # Check polled connections foreach my $connection ( @in_poll_connections ) { my $messages = $connection->getMessages(); if ( defined($messages) ) { foreach my $message ( @$messages ) { handleMessage( $connection, $message ); } } } # Check for alarms that might have happened my @out_messages; foreach my $monitor ( values(%monitors) ) { if ( ! zmMemVerify($monitor) ) { # Our attempt to verify the memory handle failed. We should reload the monitors. # Don't need to zmMemInvalidate because the monitor reload will do it. push @needsReload, $monitor; next; } my ( $state, $last_event ) = zmMemRead( $monitor, [ 'shared_data:state', 'shared_data:last_event' ] ); #print( "$monitor->{Id}: S:$state, LE:$last_event" ); #print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}" ); if ( $state == STATE_ALARM || $state == STATE_ALERT ) { # In alarm state if ( !defined($monitor->{LastEvent}) || ($last_event != $monitor->{LastEvent}) ) { # A new event push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event ); } else { # The same one as last time, so ignore it # Do nothing } } elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) || ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) ) { # Out of alarm state push( @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event ); } elsif ( defined($monitor->{LastEvent}) && ($last_event != $monitor->{LastEvent}) ) { # We've missed a whole event push( @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event ); push( @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event ); } $monitor->{LastState} = $state; $monitor->{LastEvent} = $last_event; } # end foreach monitor foreach my $connection ( @out_connections ) { if ( $connection->canWrite() ) { $connection->putMessages( \@out_messages ); } } foreach my $connection ( values(%spawned_connections) ) { if ( $connection->canWrite() ) { $connection->putMessages( \@out_messages ); } } if ( my @action_times = keys(%actions) ) { Debug( "Checking for timed actions" ); my $now = time(); foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) { Info( "Found actions expiring at $action_time" ); foreach my $action ( @{$actions{$action_time}} ) { my $connection = $action->{connection}; my $message = $action->{message}; Info( "Found action '$message'" ); handleMessage( $connection, $message ); } delete( $actions{$action_time} ); } } # end if have timed actions # Allow connections to do their own timed actions foreach my $connection ( @connections ) { my $messages = $connection->timedActions(); if ( defined($messages) ) { foreach my $message ( @$messages ) { handleMessage( $connection, $message ); } } } foreach my $connection ( values(%spawned_connections) ) { my $messages = $connection->timedActions(); if ( defined($messages) ) { foreach my $message ( @$messages ) { handleMessage( $connection, $message ); } } } # Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) { foreach my $monitor ( values(%monitors) ) { zmMemInvalidate( $monitor ); # Free up any used memory handle } loadMonitors(); @needsReload = (); # We just reloaded all monitors so no need reload a specific monitor # If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed } elsif ( @needsReload ) { foreach my $monitor ( @needsReload ) { loadMonitor($monitor); } @needsReload = (); } # zmDbConnect will ping and reconnect if neccessary $dbh = zmDbConnect(); } # end while ( 1 ) Info( "Trigger daemon exiting" ); exit; sub loadMonitor { my $monitor = shift; Debug( "Loading monitor $monitor" ); zmMemInvalidate( $monitor ); if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory $monitor->{LastState} = zmGetMonitorState( $monitor ); $monitor->{LastEvent} = zmGetLastEvent( $monitor ); } } sub loadMonitors { Debug( "Loading monitors" ); $monitor_reload_time = time(); my %new_monitors = (); my $sql = "SELECT * FROM Monitors WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )". ( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' ) ; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory $monitor->{LastState} = zmGetMonitorState( $monitor ); $monitor->{LastEvent} = zmGetLastEvent( $monitor ); } $new_monitors{$monitor->{Id}} = $monitor; } # end while fetchrow %monitors = %new_monitors; } sub handleMessage { my $connection = shift; my $message = shift; my ( $id, $action, $score, $cause, $text, $showtext ) = split( /\|/, $message ); $score = 0 if ( !defined($score) ); $cause = '' if ( !defined($cause) ); $text = '' if ( !defined($text) ); my $monitor = $monitors{$id}; if ( !$monitor ) { Warning( "Can't find monitor '$id' for message '$message'" ); return; } Debug( "Found monitor for id '$id'" ); next if ( !zmMemVerify( $monitor ) ); Debug( "Handling action '$action'" ); if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) { my $state = $1; my $delay = $2; if ( $state eq 'enable' ) { zmMonitorEnable( $monitor ); } else { zmMonitorDisable( $monitor ); } # Force a reload $monitor_reload_time = 0; Info( "Set monitor to $state" ); if ( $delay ) { my $action_text = $id.'|'.( ($state eq 'enable') ? 'disable' : 'enable' ); handleDelay($delay, $connection, $action_text); } } elsif ( $action =~ /^(on|off)(?:[ \+](\d+))?$/ ) { next if !$monitor->{Enabled}; my $trigger = $1; my $delay = $2; my $trigger_data; if ( $trigger eq 'on' ) { zmTriggerEventOn( $monitor, $score, $cause, $text ); zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); Info( "Trigger '$trigger' '$cause'" ); if ( $delay ) { my $action_text = $id.'|cancel'; handleDelay($delay, $connection, $action_text); } } elsif ( $trigger eq 'off' ) { if ( $delay ) { my $action_text = $id.'|off|0|'.$cause.'|'.$text; handleDelay($delay, $connection, $action_text); } else { my $last_event = zmGetLastEvent( $monitor ); zmTriggerEventOff( $monitor ); zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); Info( "Trigger '$trigger'" ); # Wait til it's finished while( zmInAlarm( $monitor ) && ($last_event == zmGetLastEvent( $monitor )) ) { # Tenth of a second usleep( 100000 ); } zmTriggerEventCancel( $monitor ); } } # end if trigger is on or off } elsif( $action eq 'cancel' ) { zmTriggerEventCancel( $monitor ); zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); Info( "Cancelled event" ); } elsif( $action eq 'show' ) { zmTriggerShowtext( $monitor, $showtext ); Info( "Updated show text to '$showtext'" ); } else { Error( "Unrecognised action '$action' in message '$message'" ); } } # end sub handleMessage sub handleDelay { my $delay = shift; my $connection = shift; my $action_text = shift; my $action_time = time()+$delay; my $action_array = $actions{$action_time}; if ( !$action_array ) { $action_array = $actions{$action_time} = []; } push( @$action_array, { connection=>$connection, message=>$action_text } ); Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)" ); } 1; __END__ =head1 NAME zmtrigger.pl - ZoneMinder External Trigger Script =head1 DESCRIPTION This script is used to trigger and cancel alarms from external connections using an arbitrary text based format. This script offers generic solution to external triggering of alarms. It can handle external connections via either internet socket, unix socket or file/device interfaces. You can either use it 'as is' if you can interface with the existing format, or override connections and channels to customise it to your needs. If enabled by the OPT_TRIGGERS option, Zoneminder service start zmtrigger.pl which listens for control messages on TCP port 6802. =head1 TRIGGER MESSAGE FORMAT B|B|B|B|B|B =over 4 =item B is the id number or name of the ZM monitor. =item B Valid actions are 'on', 'off', 'cancel' or 'show' where 'on' forces an alarm condition on; 'off' forces an alarm condition off; 'cancel' negates the previous 'on' or 'off'; 'show' updates the auxiliary text represented by the %Q placeholder, which can optionally be added to the affected monitor's timestamp label format. Ordinarily you would use 'on' and 'cancel', 'off' would tend to be used to suppress motion based events. Additionally 'on' and 'off' can take an additional time offset, e.g. on+20 which automatically cancel's the previous action after that number of seconds. =item B is the score given to the alarm, usually to indicate it's importance. For 'on' triggers it should be non-zero, otherwise it should be zero. =item B is a 32 char max string indicating the reason for, or source of the alarm e.g. 'Relay 1 open'. This is saved in the 'Cause' field of the event. Ignored for 'off' or 'cancel' messages. =item B is a 256 char max additional info field, which is saved in the 'Description' field of an event. Ignored for 'off' or 'cancel' messages. =item B is up to 32 characters of text that can be displayed in the timestamp that is added to images. The 'show' action is designed to update this text without affecting alarms but the text is updated, if present, for any of the actions. This is designed to allow external input to appear on the images captured, for instance temperature or personnel identity etc. =back Note that multiple messages can be sent at once and should be LF or CRLF delimited. This script is not necessarily intended to be a solution in itself, but is intended to be used as 'glue' to help ZoneMinder interface with other systems. It will almost certainly require some customisation before you can make any use of it. If all you want to do is generate alarms from external sources then using the ZoneMinder::SharedMem perl module is likely to be easier. =head1 EXAMPLES 3|on+10|1|motion|text|showtext Triggers 'alarm' on camera #3 for 10 seconds with score=1, cause='motion'. =cut ZoneMinder-1.32.2/scripts/zm.in0000644000000000000000000000575513365153155015037 0ustar rootroot#!/bin/sh # description: ZoneMinder is the top Linux video camera security and surveillance solution. ZoneMinder is intended for use in single or multi-camera video security applications.Copyright: Philip Coombes, Corey DeLasaux 2003-2008 # chkconfig: 2345 99 00 # processname: zmpkg.pl # This script is intended for use with legacy SysV init environments ONLY # For systemd environments, use the ZoneMinder systemd unit file instead # Source function library. . /etc/rc.d/init.d/functions prog=ZoneMinder ZM_CONFIG="@ZM_CONFIG@" pidfile="@ZM_RUNDIR@" LOCKFILE=/var/lock/subsys/zm loadconf() { if [ -f $ZM_CONFIG ]; then . $ZM_CONFIG else echo "ERROR: $ZM_CONFIG not found." return 1 fi } loadconf command="$ZM_PATH_BIN/zmpkg.pl" start() { # Commenting out as it is not needed. Leaving as a placeholder for future use. # zmupdate || return $? loadconf || return $? #Make sure the directory for our PID folder exists or create one. [ ! -d $pidfile ] \ && mkdir -m 774 $pidfile \ && chown $ZM_WEB_USER:$ZM_WEB_GROUP $pidfile #Make sure the folder for the socks file exists or create one GetPath="select Value from Config where Name='ZM_PATH_SOCKS'" dbHost=`echo $ZM_DB_HOST | cut -d: -f1` dbPort=`echo $ZM_DB_HOST | cut -d: -s -f2` if [ "$dbPort" = "" ] then ZM_PATH_SOCK=`echo $GetPath | mysql -B -h$ZM_DB_HOST -u$ZM_DB_USER -p$ZM_DB_PASS $ZM_DB_NAME | grep -v '^Value'` else ZM_PATH_SOCK=`echo $GetPath | mysql -B -h$dbHost -P$dbPort -u$ZM_DB_USER -p$ZM_DB_PASS $ZM_DB_NAME | grep -v '^Value'` fi [ ! -d $ZM_PATH_SOCK ] \ && mkdir -m 774 $ZM_PATH_SOCK \ && chown $ZM_WEB_USER:$ZM_WEB_GROUP $ZM_PATH_SOCK echo -n $"Starting $prog: " $command start RETVAL=$? [ $RETVAL = 0 ] && success || failure echo [ $RETVAL = 0 ] && touch $LOCKFILE return $RETVAL } stop() { loadconf echo -n $"Stopping $prog: " $command stop RETVAL=$? [ $RETVAL = 0 ] && success || failure echo [ $RETVAL = 0 ] && rm -f $LOCKFILE } zmstatus() { loadconf result=`$command status` if [ "$result" = "running" ]; then echo "ZoneMinder is running" $ZM_PATH_BIN/zmu -l RETVAL=0 else echo "ZoneMinder is stopped" RETVAL=1 fi } zmupdate() { if [ -x $ZM_PATH_BIN/zmupdate.pl ]; then $ZM_PATH_BIN/zmupdate.pl -f fi } case "$1" in 'start') start ;; 'stop') stop ;; 'restart') stop start ;; 'condrestart') loadconf result=`$ZM_PATH_BIN/zmdc.pl check` if [ "$result" = "running" ]; then $ZM_PATH_BIN/zmdc.pl shutdown > /dev/null rm -f $LOCKFILE start fi ;; 'status') status httpd status mysqld zmstatus ;; *) echo "Usage: $0 { start | stop | restart | condrestart | status }" RETVAL=1 ;; esac exit $RETVAL ZoneMinder-1.32.2/scripts/zmx10.pl.in0000644000000000000000000005765213365153155016005 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder X10 Control Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmx10.pl - ZoneMinder X10 Control Script =head1 SYNOPSIS zmx10.pl -c ,--command= [-u ,--unit-code=] =head1 DESCRIPTION This script controls the monitoring of the X10 interface and the consequent management of the ZM daemons based on the receipt of X10 signals. =head1 OPTIONS -c , --command= - Command to issue, one of 'on','off','dim','bright','status','shutdown' -u , --unit-code= - Unit code to act on required for all commands except 'status' (optional) and 'shutdown' -v, --verison - Pirnts the currently installed version of ZoneMinder =cut use strict; use bytes; # ========================================================================== # # These are the elements you can edit to suit your installation # # ========================================================================== use constant CAUSE_STRING => 'X10'; # What gets written as the cause of any events # ========================================================================== # # Don't change anything below here # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use POSIX; use Socket; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); use constant SOCK_FILE => $Config{ZM_PATH_SOCKS}.'/zmx10.sock'; $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); my $command; my $unit_code; my $version; GetOptions( 'command=s' =>\$command, 'unit-code=i' =>\$unit_code, 'version' =>\$version ) or pod2usage(-exitstatus => -1); if ( $version ) { print ZoneMinder::Base::ZM_VERSION; exit(0); } die( 'No command given' ) unless( $command ); die( 'No unit code given' ) unless( $unit_code || ($command =~ /(?:start|status|shutdown)/) ); if ( $command eq 'start' ) { X10Server::runServer(); exit(); } socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) or Fatal( "Can't open socket: $!" ); my $saddr = sockaddr_un( SOCK_FILE ); if ( !connect( CLIENT, $saddr ) ) { # The server isn't there print( "Unable to connect, starting server\n" ); close( CLIENT ); if ( my $cpid = fork() ) { # Parent process just sleep and fall through sleep( 2 ); logReinit(); socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) or Fatal( "Can't open socket: $!" ); connect( CLIENT, $saddr ) or Fatal( "Can't connect: $!" ); } elsif ( defined($cpid) ) { setpgrp(); logReinit(); X10Server::runServer(); } else { Fatal( "Can't fork: $!" ); } } # The server is there, connect to it #print( "Writing commands\n" ); CLIENT->autoflush(); my $message = "$command"; $message .= ";$unit_code" if ( $unit_code ); print( CLIENT $message ); shutdown( CLIENT, 1 ); while ( my $line = ) { chomp( $line ); print( "$line\n" ); } close( CLIENT ); #print( "Finished writing, bye\n" ); exit; # # ========================================================================== # # This is the X10 Server package # # ========================================================================== # package X10Server; use strict; use bytes; use ZoneMinder; use POSIX; use DBI; use Socket; use X10::ActiveHome; use autouse 'Data::Dumper'=>qw(Dumper); our $dbh; our $x10; our %monitor_hash; our %device_hash; our %pending_tasks; sub runServer { Info( "X10 server starting\n" ); socket( SERVER, PF_UNIX, SOCK_STREAM, 0 ) or Fatal( "Can't open socket: $!" ); unlink( main::SOCK_FILE ); my $saddr = sockaddr_un( main::SOCK_FILE ); bind( SERVER, $saddr ) or Fatal( "Can't bind: $!" ); listen( SERVER, SOMAXCONN ) or Fatal( "Can't listen: $!" ); $dbh = zmDbConnect(); $x10 = new X10::ActiveHome( port=>$Config{ZM_X10_DEVICE}, house_code=>$Config{ZM_X10_HOUSE_CODE}, debug=>0 ); loadTasks(); $x10->register_listener( \&x10listen ); my $rin = ''; vec( $rin, fileno(SERVER),1) = 1; vec( $rin, $x10->select_fds(),1) = 1; my $timeout = 0.2; #print( 'F:'.fileno(SERVER)."\n" ); my $reload = undef; my $reload_count = 0; my $reload_limit = $Config{ZM_X10_DB_RELOAD_INTERVAL} / $timeout; while( 1 ) { my $nfound = select( my $rout = $rin, undef, undef, $timeout ); #print( "Off select, NF:$nfound, ER:$!\n" ); #print( vec( $rout, fileno(SERVER),1)."\n" ); #print( vec( $rout, $x10->select_fds(),1)."\n" ); if ( $nfound > 0 ) { if ( vec( $rout, fileno(SERVER),1) ) { my $paddr = accept( CLIENT, SERVER ); my $message = ; my ( $command, $unit_code ) = split( /;/, $message ); my $device; if ( defined($unit_code) ) { if ( $unit_code < 1 || $unit_code > 16 ) { dPrint( ZoneMinder::Logger::ERROR, "Invalid unit code '$unit_code'\n" ); next; } $device = $device_hash{$unit_code}; if ( !$device ) { $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), status=>'unknown' }; } } my $result; if ( $command eq 'on' ) { $result = $device->{appliance}->on(); } elsif ( $command eq 'off' ) { $result = $device->{appliance}->off(); } #elsif ( $command eq 'dim' ) #{ #$result = $device->{appliance}->dim(); #} #elsif ( $command eq 'bright' ) #{ #$result = $device->{appliance}->bright(); #} elsif ( $command eq 'status' ) { if ( $device ) { dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); } else { foreach my $unit_code ( sort( keys(%device_hash) ) ) { my $device = $device_hash{$unit_code}; dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); } } } elsif ( $command eq 'shutdown' ) { last; } else { dPrint( ZoneMinder::Logger::ERROR, "Invalid command '$command'\n" ); } if ( defined($result) ) { if ( 1 || $result ) { $device->{status} = uc($command); dPrint( ZoneMinder::Logger::DEBUG, $device->{appliance}->address()." $command, ok\n" ); #x10listen( new X10::Event( sprintf("%s %s", $device->{appliance}->address, uc($command) ) ) ); } else { dPrint( ZoneMinder::Logger::ERROR, $device->{appliance}->address()." $command, failed\n" ); } } close( CLIENT ); } elsif ( vec( $rout, $x10->select_fds(),1) ) { $x10->handle_input(); } else { Fatal( 'Bogus descriptor' ); } } elsif ( $nfound < 0 ) { if ( $! != EINTR ) { Fatal( "Can't select: $!" ); } } else { #print( "Select timed out\n" ); # Check for state changes foreach my $monitor_id ( sort(keys(%monitor_hash) ) ) { my $monitor = $monitor_hash{$monitor_id}; my $state = zmGetMonitorState( $monitor ); if ( !defined($state) ) { $reload = !undef; next; } if ( defined( $monitor->{LastState} ) ) { my $task_list; if ( ($state == STATE_ALARM || $state == STATE_ALERT) && ($monitor->{LastState} == STATE_IDLE || $monitor->{LastState} == STATE_TAPE) ) # Gone into alarm state { Debug( "Applying ON_list for $monitor_id\n" ); $task_list = $monitor->{'ON_list'}; } elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) || ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) ) # Come out of alarm state { Debug( "Applying OFF_list for $monitor_id\n" ); $task_list = $monitor->{'OFF_list'}; } if ( $task_list ) { foreach my $task ( @$task_list ) { processTask( $task ); } } } $monitor->{LastState} = $state; } # Check for pending tasks my $now = time(); foreach my $activation_time ( sort(keys(%pending_tasks) ) ) { last if ( $activation_time > $now ); my $pending_list = $pending_tasks{$activation_time}; foreach my $task ( @$pending_list ) { processTask( $task ); } delete( $pending_tasks{$activation_time} ); } if ( $reload || ++$reload_count >= $reload_limit ) { loadTasks(); $reload = undef; $reload_count = 0; } } } Info( "X10 server exiting\n" ); close( SERVER ); exit(); } sub addToDeviceList { my $unit_code = shift; my $event = shift; my $monitor = shift; my $function = shift; my $limit = shift; Debug( "Adding to device list, uc:$unit_code, ev:$event, mo:" .$monitor->{Id}.", fu:$function, li:$limit\n" ); my $device = $device_hash{$unit_code}; if ( !$device ) { $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), status=>'unknown' }; } my $task = { type=>'device', monitor=>$monitor, address=>$device->{appliance}->address(), function=>$function }; if ( $limit ) { $task->{limit} = $limit } my $task_list = $device->{$event.'_list'}; if ( !$task_list ) { $task_list = $device->{$event.'_list'} = []; } push( @$task_list, $task ); } sub addToMonitorList { my $monitor = shift; my $event = shift; my $unit_code = shift; my $function = shift; my $limit = shift; Debug( "Adding to monitor list, uc:$unit_code, ev:$event, mo:".$monitor->{Id} .", fu:$function, li:$limit\n" ); my $device = $device_hash{$unit_code}; if ( !$device ) { $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), status=>'unknown' }; } my $task = { type=>'monitor', device=>$device, id=>$monitor->{Id}, function=>$function }; if ( $limit ) { $task->{limit} = $limit; } my $task_list = $monitor->{$event.'_list'}; if ( !$task_list ) { $task_list = $monitor->{$event.'_list'} = []; } push( @$task_list, $task ); } sub loadTasks { %monitor_hash = (); Debug( "Loading tasks\n" ); # Clear out all old device task lists foreach my $unit_code ( sort( keys(%device_hash) ) ) { my $device = $device_hash{$unit_code}; $device->{ON_list} = []; $device->{OFF_list} = []; } my $sql = "SELECT M.*,T.* from Monitors as M INNER JOIN TriggersX10 as T on (M.Id = T.MonitorId) WHERE find_in_set( M.Function, 'Modect,Record,Mocord,Nodect' ) AND M.Enabled = 1 AND find_IN_set( 'X10', M.Triggers )" ; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { # Check shared memory ok if ( !zmMemVerify( $monitor ) ) { zmMemInvalidate( $monitor ); next ; } $monitor_hash{$monitor->{Id}} = $monitor; if ( $monitor->{Activation} ) { Debug( "$monitor->{Name} has active string '$monitor->{Activation}'\n" ); foreach my $code_string ( split( /,/, $monitor->{Activation} ) ) { #Debug( "Code string: $code_string\n" ); my ( $invert, $unit_code, $modifier, $limit ) = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); $limit = 0 if ( !$limit ); if ( $unit_code ) { if ( !$modifier || $modifier eq '+' ) { addToDeviceList( $unit_code, 'ON', $monitor, !$invert ? 'start_active' : 'stop_active', $limit ); } if ( !$modifier || $modifier eq '-' ) { addToDeviceList( $unit_code, 'OFF', $monitor, !$invert ? 'stop_active' : 'start_active', $limit ); } } } } if ( $monitor->{AlarmInput} ) { Debug( "$monitor->{Name} has alarm input string '$monitor->{AlarmInput}'\n" ); foreach my $code_string ( split( /,/, $monitor->{AlarmInput} ) ) { #Debug( "Code string: $code_string\n" ); my ( $invert, $unit_code, $modifier, $limit ) = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); $limit = 0 if ( !$limit ); if ( $unit_code ) { if ( !$modifier || $modifier eq '+' ) { addToDeviceList( $unit_code, 'ON', $monitor, !$invert ? 'start_alarm' : 'stop_alarm', $limit ); } if ( !$modifier || $modifier eq '-' ) { addToDeviceList( $unit_code, 'OFF', $monitor, !$invert ? 'stop_alarm' : 'start_alarm', $limit ); } } } } if ( $monitor->{AlarmOutput} ) { Debug( "$monitor->{Name} has alarm output string '$monitor->{AlarmOutput}'\n" ); foreach my $code_string ( split( /,/, $monitor->{AlarmOutput} ) ) { #Debug( "Code string: $code_string\n" ); my ( $invert, $unit_code, $modifier, $limit ) = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); $limit = 0 if ( !$limit ); if ( $unit_code ) { if ( !$modifier || $modifier eq '+' ) { addToMonitorList( $monitor, 'ON', $unit_code, !$invert ? 'on' : 'off', $limit ); } if ( !$modifier || $modifier eq '-' ) { addToMonitorList( $monitor, 'OFF', $unit_code, !$invert ? 'off' : 'on', $limit ); } } } } zmMemInvalidate( $monitor ); } } sub addPendingTask { my $task = shift; # Check whether we are just extending a previous pending task # and remove it if it's there foreach my $activation_time ( sort(keys(%pending_tasks) ) ) { my $pending_list = $pending_tasks{$activation_time}; my $new_pending_list = []; foreach my $pending_task ( @$pending_list ) { if ( $task->{type} ne $pending_task->{type} ) { push( @$new_pending_list, $pending_task ) } elsif ( $task->{type} eq 'device' ) { if (( $task->{monitor}->{Id} != $pending_task->{monitor}->{Id} ) || ( $task->{function} ne $pending_task->{function} )) { push( @$new_pending_list, $pending_task ) } } elsif ( $task->{type} eq 'monitor' ) { if (( $task->{device}->{appliance}->unit_code() != $pending_task->{device}->{appliance}->unit_code() ) || ( $task->{function} ne $pending_task->{function} ) ) { push( @$new_pending_list, $pending_task ) } } } if ( @$new_pending_list ) { $pending_tasks{$activation_time} = $new_pending_list; } else { delete( $pending_tasks{$activation_time} ); } } my $end_time = time() + $task->{limit}; my $pending_list = $pending_tasks{$end_time}; if ( !$pending_list ) { $pending_list = $pending_tasks{$end_time} = []; } my $pending_task; if ( $task->{type} eq 'device' ) { $pending_task = { type=>$task->{type}, monitor=>$task->{monitor}, function=>$task->{function} }; $pending_task->{function} =~ s/start/stop/; } elsif ( $task->{type} eq 'monitor' ) { $pending_task = { type=>$task->{type}, device=>$task->{device}, function=>$task->{function} }; $pending_task->{function} =~ s/on/off/; } push( @$pending_list, $pending_task ); } sub processTask { my $task = shift; if ( $task->{type} eq 'device' ) { my ( $instruction, $class ) = ( $task->{function} =~ /^(.+)_(.+)$/ ); if ( $class eq 'active' ) { if ( $instruction eq 'start' ) { zmMonitorEnable( $task->{monitor} ); if ( $task->{limit} ) { addPendingTask( $task ); } } elsif( $instruction eq 'stop' ) { zmMonitorDisable( $task->{monitor} ); } } elsif( $class eq 'alarm' ) { if ( $instruction eq 'start' ) { zmTriggerEventOn( $task->{monitor}, 0, main::CAUSE_STRING, $task->{address} ); if ( $task->{limit} ) { addPendingTask( $task ); } } elsif( $instruction eq 'stop' ) { zmTriggerEventCancel( $task->{monitor} ); } } } elsif( $task->{type} eq 'monitor' ) { if ( $task->{function} eq 'on' ) { $task->{device}->{appliance}->on(); if ( $task->{limit} ) { addPendingTask( $task ); } } elsif ( $task->{function} eq 'off' ) { $task->{device}->{appliance}->off(); } } } sub dPrint { my $dbg_level = shift; if ( fileno(CLIENT) ) { print CLIENT @_ } if ( $dbg_level == ZoneMinder::Logger::DEBUG ) { Debug( @_ ); } elsif ( $dbg_level == ZoneMinder::Logger::INFO ) { Info( @_ ); } elsif ( $dbg_level == ZoneMinder::Logger::WARNING ) { Warning( @_ ); } elsif ( $dbg_level == ZoneMinder::Logger::ERROR ) { Error( @_ ); } elsif ( $dbg_level == ZoneMinder::Logger::FATAL ) { Fatal( @_ ); } } sub x10listen { foreach my $event ( @_ ) { #print( Data::Dumper( $_ )."\n" ); if ( $event->house_code() eq $Config{ZM_X10_HOUSE_CODE} ) { my $unit_code = $event->unit_code(); my $device = $device_hash{$unit_code}; if ( !$device ) { $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), status=>'unknown' }; } next if ( $event->func() !~ /(?:ON|OFF)/ ); $device->{status} = $event->func(); my $task_list = $device->{$event->func().'_list'}; if ( $task_list ) { foreach my $task ( @$task_list ) { processTask( $task ); } } } Info( "Got event - ".$event->as_string()."\n" ); } } 1; ZoneMinder-1.32.2/scripts/zmpkg.pl.in0000644000000000000000000003253213365153155016144 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder Package Control Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== use strict; use bytes; # ========================================================================== # # Don't change anything below here # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use DBI; use POSIX; use Time::HiRes qw/gettimeofday/; use autouse 'Pod::Usage'=>qw(pod2usage); # Detaint our environment $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $store_state=''; # PP - will remember state name passed logInit(); my $command = $ARGV[0]||''; if ( $command eq 'version' ) { print ZoneMinder::Base::ZM_VERSION . "\n"; exit(0); } my $state; my $dbh = zmDbConnect(); Info("Command: $command"); if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ ) { if ( $command ) { # Check to see if it's a valid run state my $sql = 'SELECT * FROM States WHERE Name=?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($command) or Fatal("Can't execute: ".$sth->errstr()); if ( $state = $sth->fetchrow_hashref() ) { #$state->{Name} = $command; $state->{Definitions} = []; foreach( split(',', $state->{Definition}) ) { my ( $id, $function, $enabled ) = split(':', $_); push( @{$state->{Definitions}}, { Id=>$id, Function=>$function, Enabled=>$enabled } ); } $store_state = $command; # PP - Remember the name that was passed to search in DB $command = 'state'; } else { $command = undef; } } if ( !$command ) { pod2usage(-exitstatus => -1); } } # end if not one of the usual commands # PP - Sane state check Debug("StartisActiveSSantiyCheck"); isActiveSanityCheck(); Debug("Done isActiveSSantiyCheck"); # Move to the right place chdir($Config{ZM_PATH_WEB}) or Fatal("Can't chdir to '$Config{ZM_PATH_WEB}': $!"); my $dbg_id = ''; Info("Command: $command"); my $retval = 0; if ( $command eq 'state' ) { Info("Updating DB: $state->{Name}"); my $sql = 'SELECT * FROM Monitors' . ($Config{ZM_SERVER_ID} ? ' WHERE ServerId=?' : '' ) .' ORDER BY Id ASC'; my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID}: ()) or Fatal("Can't execute: ".$sth->errstr()); while( my $monitor = $sth->fetchrow_hashref() ) { foreach my $definition ( @{$state->{Definitions}} ) { if ( $monitor->{Id} =~ /^$definition->{Id}$/ ) { $monitor->{NewFunction} = $definition->{Function}; $monitor->{NewEnabled} = $definition->{Enabled}; } } #next if ( !$monitor->{NewFunction} ); $monitor->{NewFunction} = 'None' if ( !$monitor->{NewFunction} ); $monitor->{NewEnabled} = 0 if ( !$monitor->{NewEnabled} ); if ( $monitor->{Function} ne $monitor->{NewFunction} || $monitor->{Enabled} ne $monitor->{NewEnabled} ) { my $sql = 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($monitor->{NewFunction}, $monitor->{NewEnabled}, $monitor->{Id}) or Fatal("Can't execute: ".$sth->errstr()); } # end if change of function or enablement } # end foreach monitor $sth->finish(); # PP - Now mark a specific state as active resetStates(); Info("Marking $store_state as Enabled"); $sql = 'UPDATE States SET IsActive = 1 WHERE Name = ?'; $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute($store_state) or Fatal("Can't execute: ".$sth->errstr()); # PP - zero out other states isActive $command = 'restart'; } # end if command = state # Check if we are running systemd and if we have been called by the system if ( $command =~ /^(start|stop|restart)$/ ) { # We have to detaint to keep perl from complaining $command = $1; if ( systemdRunning() && !calledBysystem() ) { qx(@BINDIR@/zmsystemctl.pl $command); $command = ''; } } if ( $command =~ /^(?:stop|restart)$/ ) { my $status = runCommand('zmdc.pl check'); Debug("zmdc.pl check = $status"); if ( $status eq 'running' ) { runCommand('zmdc.pl shutdown'); zmMemTidy(); } else { $retval = 1; } } if ( $command =~ /^(?:start|restart)$/ ) { my $status = runCommand('zmdc.pl check'); Debug("zmdc.pl check = $status"); if ( $status eq 'stopped' ) { if ( $Config{ZM_DYN_DB_VERSION} and ( $Config{ZM_DYN_DB_VERSION} ne ZM_VERSION ) ) { Fatal('Version mismatch, system is version '.ZM_VERSION .', database is '.$Config{ZM_DYN_DB_VERSION} .', please run zmupdate.pl to update.' ); exit(-1); } # Recreate the temporary directory if it's been wiped verifyFolder('@ZM_TMPDIR@'); # Recreate the run directory if it's been wiped verifyFolder('@ZM_RUNDIR@'); # Recreate the sock directory if it's been wiped verifyFolder('@ZM_SOCKDIR@'); zmMemTidy(); runCommand('zmdc.pl startup'); my $Server = undef; my $sql; my @values; if ( $Config{ZM_SERVER_ID} ) { require ZoneMinder::Server; Info("Multi-server configuration detected. Starting up services for server $Config{ZM_SERVER_ID}"); $Server = new ZoneMinder::Server($Config{ZM_SERVER_ID}); $sql = 'SELECT * FROM Monitors WHERE ServerId=?'; @values = ( $Config{ZM_SERVER_ID} ); } else { Info('Single server configuration detected. Starting up services.'); $sql = 'SELECT * FROM Monitors'; } { my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute(@values) or Fatal("Can't execute: ".$sth->errstr()); while( my $monitor = $sth->fetchrow_hashref() ) { if ( $monitor->{Function} ne 'None' && $monitor->{Type} ne 'WebSite' ) { if ( $monitor->{Type} eq 'Local' ) { runCommand("zmdc.pl start zmc -d $monitor->{Device}"); } else { runCommand("zmdc.pl start zmc -m $monitor->{Id}"); } if ( $monitor->{Function} ne 'Monitor' ) { runCommand("zmdc.pl start zma -m $monitor->{Id}"); } if ( $Config{ZM_OPT_CONTROL} ) { if ( $monitor->{Controllable} && $monitor->{TrackMotion} ) { if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) { runCommand( "zmdc.pl start zmtrack.pl -m $monitor->{Id}" ); } else { Warning(' Monitor is set to track motion, but does not have motion detection enabled.'); } # end if Has motion enabled } # end if track motion } # end if ZM_OPT_CONTROL } # end if function is not none or Website } # end foreach monitor $sth->finish(); } { my $sql = 'SELECT Id FROM Filters WHERE Background=1'; my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); if ( $sth->rows ) { while( my $filter = $sth->fetchrow_hashref() ) { # This is now started unconditionally runCommand("zmdc.pl start zmfilter.pl --filter_id=$$filter{Id} --daemon"); } } else { runCommand('zmdc.pl start zmfilter.pl'); } $sth->finish(); } if ( $Config{ZM_RUN_AUDIT} ) { if ( $Server and exists $$Server{'zmaudit'} and ! $$Server{'zmaudit'} ) { Debug("Not running zmaudit.pl because it is turned off for this server."); } else { runCommand('zmdc.pl start zmaudit.pl -c'); } } if ( $Config{ZM_OPT_TRIGGERS} ) { if ( $Server and exists $$Server{'zmtrigger'} and ! $$Server{'zmtrigger'} ) { Debug("Not running zmtrigger.pl because it is turned off for this server."); } else { runCommand('zmdc.pl start zmtrigger.pl'); } } if ( $Config{ZM_OPT_X10} ) { runCommand('zmdc.pl start zmx10.pl -c start'); } runCommand('zmdc.pl start zmwatch.pl'); if ( $Config{ZM_CHECK_FOR_UPDATES} ) { runCommand('zmdc.pl start zmupdate.pl -c'); } if ( $Config{ZM_TELEMETRY_DATA} ) { runCommand('zmdc.pl start zmtelemetry.pl'); } if ($Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { runCommand('zmdc.pl start zmeventnotification.pl'); } if ( $Server and exists $$Server{'zmstats'} and ! $$Server{'zmstats'} ) { Debug("Not running zmstats.pl because it is turned off for this server."); } else { runCommand('zmdc.pl start zmstats.pl'); } } else { $retval = 1; } } # end if command is start or restart if ( $command eq 'status' ) { my $status = runCommand('zmdc.pl check'); print(STDOUT $status."\n"); } elsif ( $command eq 'logrot' ) { runCommand('zmdc.pl logrot'); } exit($retval); # PP - Make sure isActive is on and only one sub isActiveSanityCheck { Info('Sanity checking States table...'); $dbh = zmDbConnect() if ! $dbh; # PP - First, make sure default exists and there is only one my $sql = q`SELECT Name FROM States WHERE Name='default'`; my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); if ( $sth->rows != 1 ) { # PP - no row, or too many rows. Either case is an error Info( 'Fixing States table - either no default state or duplicate default states' ); $sql = "DELETE FROM States WHERE Name='default'"; $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute() or Fatal( "Can't execute: ".$sth->errstr() ); $sql = q`"INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`; $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); } # PP - Now make sure no two states have IsActive=1 $sql = 'SELECT Name FROM States WHERE IsActive = 1'; $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); if ( $sth->rows != 1 ) { Info('Fixing States table so only one run state is active'); resetStates(); $sql = q`UPDATE States SET IsActive=1 WHERE Name='default'`; $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); } } # end sub isActiveSanityCheck # PP - zeroes out isActive for all states sub resetStates { $dbh = zmDbConnect() if ! $dbh; my $sql = 'UPDATE States SET IsActive=0'; my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute() or Fatal("Can't execute: ".$sth->errstr()); } sub systemdRunning { my $output = qx(ps -o comm="" -p 1); return scalar ( $output =~ /systemd/ ); } sub calledBysystem { my $ppid = getppid(); my $output = qx(ps -o comm="" -p $ppid); #chomp( $output ); return ($output =~ /^(?:systemd|init)$/); } sub verifyFolder { my $folder = shift; # Recreate the temporary directory if it's been wiped if ( !-e $folder ) { Debug("Recreating directory '$folder'"); mkdir($folder, 0774) or Fatal( "Can't create missing temporary directory '$folder': $!" ); my ( $runName ) = getpwuid($>); if ( $runName ne $Config{ZM_WEB_USER} ) { # Not running as web user, so should be root in which case # chown the directory my ( $webName, $webPass, $webUid, $webGid ) = getpwnam($Config{ZM_WEB_USER}) or Fatal("Can't get details for web user '$Config{ZM_WEB_USER}': $!"); chown($webUid, $webGid, $folder) or Fatal("Can't change ownership of '$folder' to '" .$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!" ); } # end if runName ne ZM_WEB_USER } # end if folder doesn't exist } # end sub verifyFolder 1; __END__ =head1 NAME zmpkg.pl - ZoneMinder Package Control Script =head1 SYNOPSIS zmpkg.pl {start|stop|restart|status|logrot|'state'|version} =head1 DESCRIPTION This script is used to start and stop the ZoneMinder package primarily to allow command line control for automatic restart on reboot (see zm script) =cut ZoneMinder-1.32.2/scripts/zmfilter.pl.in0000644000000000000000000007445413365153155016661 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder Event Filter Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmfilter.pl - ZoneMinder tool to filter events =head1 SYNOPSIS zmfilter.pl [-f ,--filter=] [--filter_id=] | -v, --version =head1 DESCRIPTION This script continuously monitors the recorded events for the given monitor and applies any filters which would delete and/or upload matching events. =head1 OPTIONS -f{filter name}, --filter={filter name} - The name of a specific filter to run --filter_id={filter id} - The id of a specific filter to run -v, --version - Print ZoneMinder version =cut use strict; use bytes; # ========================================================================== # # These are the elements you can edit to suit your installation # # ========================================================================== use constant START_DELAY => 5; # How long to wait before starting # ========================================================================== # # You shouldn't need to change anything from here downwards # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use DBI; use POSIX; use Time::HiRes qw/gettimeofday/; #use Date::Manip; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); use File::Path qw(make_path); my $daemon = 0; my $filter_name = ''; my $filter_id; my $version = 0; my $zm_terminate = 0; GetOptions( daemon =>\$daemon, 'filter=s' =>\$filter_name, 'filter_id=s' =>\$filter_id, version =>\$version ) or pod2usage(-exitstatus => -1); if ( $version ) { print ZoneMinder::Base::ZM_VERSION . "\n"; exit(0); } require ZoneMinder::Event; require ZoneMinder::Filter; use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) ? $Config{ZM_DIR_EVENTS} : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) ; logInit($filter_id?(id=>'zmfilter_'.$filter_id):()); sub HupHandler { # This idea at this time is to just exit, freeing up the memory. # zmfilter.pl will be respawned by zmdc. TermHandler(); return; Info('Received HUP, reloading'); ZoneMinder::Object::init_cache(); &ZoneMinder::Logger::logHupHandler(); } sub TermHandler { Info('Received TERM, exiting'); $zm_terminate = 1; } sub Term { exit(0); } $SIG{HUP} = \&HupHandler; $SIG{TERM} = \&TermHandler; $SIG{INT} = \&TermHandler; if ( $Config{ZM_OPT_UPLOAD} ) { # Comment these out if you don't have them and don't want to upload # or don't want to use that format if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { require Archive::Zip; import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); } else { require Archive::Tar; } if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { require Net::FTP; } else { require Net::SFTP::Foreign; } } if ( $Config{ZM_OPT_EMAIL} ) { if ( $Config{ZM_NEW_MAIL_MODULES} ) { require MIME::Lite; require Net::SMTP; } else { require MIME::Entity; } } if ( $Config{ZM_OPT_MESSAGE} ) { if ( $Config{ZM_NEW_MAIL_MODULES} ) { require MIME::Lite; require Net::SMTP; } else { require MIME::Entity; } } $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; if ( ! EVENT_PATH ) { Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}"); die; } # In future, should not be neccessary wrt StorageAreas chdir( EVENT_PATH ); # Should not be neccessary... but nice to get a local var. What if it fails? my $dbh = zmDbConnect(); if ( $filter_name ) { Info("Scanning for events using filter '$filter_name'"); } elsif ( $filter_id ) { Info("Scanning for events using filter id '$filter_id'"); } else { Info("Scanning for events using all filters"); } if ( ! ( $filter_name or $filter_id ) ) { Debug('Sleeping due to start delay: ' . START_DELAY . ' seconds...'); sleep(START_DELAY); } my @filters; my $last_action = 0; while( !$zm_terminate ) { my $now = time; if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { Debug("Reloading filters"); $last_action = $now; @filters = getFilters({ Name=>$filter_name, Id=>$filter_id }); } foreach my $filter ( @filters ) { last if $zm_terminate; if ( $$filter{Concurrent} and ! ( $filter_id or $filter_name ) ) { my ( $proc ) = $0 =~ /(\S+)/; my ( $id ) = $$filter{Id} =~ /(\d+)/; Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}"); system(qq`$proc --filter "$$filter{Name}" &`); } else { checkFilter($filter); } } last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate; Debug("Sleeping for $delay seconds"); sleep($delay); } sub getFilters { my $sql_filters = @_ ? shift : {}; my @sql_values; my @filters; my $sql = 'SELECT * FROM Filters WHERE'; if ( $$sql_filters{Name} ) { $sql .= ' Name = ? AND'; push @sql_values, $$sql_filters{Name}; } elsif ( $$sql_filters{Id} ) { $sql .= ' Id = ? AND'; push @sql_values, $$sql_filters{Id}; } else { $sql .= ' Background = 1 AND'; } $sql .= '( AutoArchive = 1 or AutoVideo = 1 or AutoUpload = 1 or AutoEmail = 1 or AutoMessage = 1 or AutoExecute = 1 or AutoDelete = 1 or UpdateDiskSpace = 1 or AutoMove = 1 ) ORDER BY Name'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute(@sql_values) or Fatal("Unable to execute '$sql': ".$dbh->errstr()); FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { my $filter = new ZoneMinder::Filter($$db_filter{Id}, $db_filter); Debug("Found filter '$db_filter->{Name}'"); # The undef here is to make sure the Sql gets regenerated because the Filter object may be cached my $filter_sql = $filter->Sql(undef); if ( ! $filter_sql ) { Error("Error parsing Sql. skipping filter '$db_filter->{Name}'"); next FILTER; } push @filters, $filter; } $sth->finish(); if ( ! @filters ) { Warning("No filter found for $sql with values(@sql_values)"); } else { Debug("Got " . @filters . " filters"); } return @filters; } # end sub getFilters sub checkFilter { my $filter = shift; my @Events = $filter->Execute(); Info( join(' ', 'Checking filter', $filter->{Name}, join(', ', ($filter->{AutoDelete}?'delete':()), ($filter->{AutoArchive}?'archive':()), ($filter->{AutoVideo}?'video':()), ($filter->{AutoUpload}?'upload':()), ($filter->{AutoEmail}?'email':()), ($filter->{AutoMessage}?'message':()), ($filter->{AutoExecute}?'execute':()), ($filter->{AutoMove}?'move':()), ($filter->{UpdateDiskSpace}?'update disk space':()), ), 'returned' , scalar @Events , 'events', "\n", ) ); foreach my $event ( @Events ) { last if $zm_terminate; my $Event = new ZoneMinder::Event($$event{Id}, $event); Debug("Checking event $Event->{Id}"); my $delete_ok = !undef; $dbh->ping(); if ( $filter->{AutoArchive} ) { Info("Archiving event $Event->{Id}"); # Do it individually to avoid locking up the table for new events my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached( $sql ) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute( $Event->{Id} ) or Error("Unable to execute '$sql': ".$dbh->errstr()); } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { if ( !$Event->{Videoed} ) { $delete_ok = undef if !generateVideo($filter, $Event); } } if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) { if ( !$Event->{Emailed} ) { $delete_ok = undef if !sendEmail($filter, $Event); } } if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { if ( !$Event->{Messaged} ) { $delete_ok = undef if !sendMessage($filter, $Event); } } if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { if ( !$Event->{Uploaded} ) { $delete_ok = undef if !uploadArchFile($filter, $Event); } } if ( $filter->{AutoExecute} ) { if ( !$Event->{Executed} ) { $delete_ok = undef if !executeCommand($filter, $Event); } } if ( $filter->{AutoDelete} ) { if ( $delete_ok ) { $Event->delete(); } else { Error("Unable to delete event $Event->{Id} as previous operations failed"); } } # end if AutoDelete if ( $filter->{AutoMove} ) { my $NewStorage = new ZoneMinder::Storage($filter->{AutoMoveTo}); $_ = $Event->MoveTo($NewStorage); Error($_) if $_; } if ( $filter->{UpdateDiskSpace} ) { $ZoneMinder::Database::dbh->begin_work(); $Event->lock_and_load(); my $old_diskspace = $$Event{DiskSpace}; my $new_diskspace = $Event->DiskSpace(undef); if ( ( (!defined $old_diskspace) and defined $new_diskspace) or ( (defined $old_diskspace) and (defined $new_diskspace) and ( $old_diskspace != $Event->DiskSpace(undef) ) ) ) { $Event->save(); } $ZoneMinder::Database::dbh->commit(); } # end if UpdateDiskSpace } # end foreach event } sub generateVideo { my $filter = shift; my $Event = shift; my $phone = shift; my $rate = $Event->{DefaultRate}/100; my $scale = $Event->{DefaultScale}/100; my $format; my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS}); my $default_video_format; my $default_phone_format; foreach my $ffmpeg_format( @ffmpeg_formats ) { if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) { $default_phone_format = $1; } elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) { $default_video_format = $1; } } if ( $phone && $default_phone_format ) { $format = $default_phone_format; } elsif ( $default_video_format ) { $format = $default_video_format; } else { $format = $ffmpeg_formats[0]; } my $command = join('', $Config{ZM_PATH_BIN}, '/zmvideo.pl -e ', $Event->{Id}, ' -r ', $rate, ' -s ', $scale, ' -f ', $format, ); my $output = qx($command); chomp($output); my $status = $? >> 8; if ( $status || logDebugging() ) { Debug("Output: $output"); } if ( $status ) { Error("Video generation '$command' failed with status: $status"); if ( wantarray() ) { return( undef, undef ); } return 0; } else { my $sql = 'UPDATE Events SET Videoed = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Event->{Id}) or Fatal("Unable to execute '$sql': ".$dbh->errstr()); if ( wantarray() ) { return( $format, $output ); } } return 1; } # Returns an image absolute path for given event and frame # Optionally an analyse image path may be returned if an analyse image exists # If neither capture nor analyse image exists it will try to extract a frame from .mp4 file if exists # An empty string is returned if no one from methods above works sub generateImage { my $Event = shift; my $frame = shift; my $analyse = @_ ? shift : 0; # don't return analyse image by default my $event_path = $Event->Path(); my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', $event_path, $frame->{FrameId}); my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', $event_path, $frame->{FrameId}) if $analyse; my $video_path = sprintf('%s/%d-video.mp4', $event_path, $Event->{Id}); my $image_path = ''; # check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video if ( $analyse && -r $analyse_image_path ) { $image_path = $analyse_image_path; } elsif ( -r $capture_image_path ) { $image_path = $capture_image_path; } elsif ( -r $video_path ) { my $command ="ffmpeg -ss $$frame{Delta} -i '$video_path' -frames:v 1 '$capture_image_path'"; #$command = "ffmpeg -y -v 0 -i $video_path -vf 'select=gte(n\\,$$frame{FrameId}),setpts=PTS-STARTPTS' -vframes 1 -f image2 $capture_image_path"; my $output = qx($command); chomp( $output ); my $status = $? >> 8; if ( $status || logDebugging() ) { Debug("Output: $output"); } if ( $status ) { Error("Failed $command status $status"); } else { $image_path = $capture_image_path; } } return $image_path; } sub uploadArchFile { my $filter = shift; my $Event = shift; if ( ! $Config{ZM_UPLOAD_HOST} ) { Error('Cannot upload archive as no upload host defined'); return( 0 ); } # Try to make the path to the upload folder if it doesn't already exist make_path( $Config{ZM_UPLOAD_LOC_DIR} ); # Complain if the upload folder is still not writable if ( ! -w $Config{ZM_UPLOAD_LOC_DIR} ) { Error("Upload folder either does not exist or is not writable: $Config{ZM_UPLOAD_LOC_DIR}"); return(0); } my $archFile = $Event->{MonitorName}.'-'.$Event->{Id}; my $archImagePath = $Event->Path() .'/' .( ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) ? '{*analyse.jpg,*capture.jpg,snapshot.jpg,*.mp4}' : '{*capture.jpg,snapshot.jpg,*.mp4}' ) ; my @archImageFiles = glob($archImagePath); my $archLocPath; my $archError = 0; if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { $archFile .= '.zip'; $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; my $zip = Archive::Zip->new(); Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files'); my $status = &AZ_OK; foreach my $imageFile ( @archImageFiles ) { Debug("Adding $imageFile"); my $member = $zip->addFile($imageFile); if ( !$member ) { Error("Unable toto add image file $imageFile to zip archive $archLocPath"); $archError = 1; last; } $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} ? &COMPRESSION_DEFLATED : &COMPRESSION_STORED ); } if ( !$archError ) { $status = $zip->writeToFileNamed( $archLocPath ); if ( $archError = ($status != &AZ_OK) ) { Error("Zip error: $status"); } } else { Error("Error adding images to zip archive $archLocPath, not writing"); } } elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'tar' ) { if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) { $archFile .= '.tar.gz'; } else { $archFile .= '.tar'; } $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files'); if ( $archError = !Archive::Tar->create_archive( $archLocPath, $Config{ZM_UPLOAD_ARCH_COMPRESS}, @archImageFiles ) ) { Error('Tar error: '.Archive::Tar->error()); } } if ( $archError ) { return( 0 ); } else { if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { Info('Uploading to '.$Config{ZM_UPLOAD_HOST}.' using FTP'); my $ftp = Net::FTP->new( $Config{ZM_UPLOAD_HOST}, Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, Debug=>$Config{ZM_UPLOAD_DEBUG} ); if ( !$ftp ) { Error("Unable to create FTP connection: $@"); return 0; } $ftp->login($Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS}) or Error("FTP - Unable to login"); $ftp->binary() or Error("FTP - Unable to go binary"); $ftp->cwd($Config{ZM_UPLOAD_REM_DIR}) or Error("FTP - Unable to cwd") if ( $Config{ZM_UPLOAD_REM_DIR} ); $ftp->put( $archLocPath ) or Error("FTP - Unable to upload '$archLocPath'"); $ftp->quit() or Error("FTP - Unable to quit"); } else { my $host = $Config{ZM_UPLOAD_HOST}; $host .= ':'.$Config{ZM_UPLOAD_PORT} if $Config{ZM_UPLOAD_PORT}; Info('Uploading to '.$host.' using SFTP'); my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER}, ($Config{ZM_UPLOAD_PASS} ? (password=>$Config{ZM_UPLOAD_PASS}) : ()), ($Config{ZM_UPLOAD_PORT} ? (port=>$Config{ZM_UPLOAD_PORT}) : ()), ($Config{ZM_UPLOAD_TIMEOUT} ? (timeout=>$Config{ZM_UPLOAD_TIMEOUT}) : ()), ); my @more_ssh_args; push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' if ! $Config{ZM_UPLOAD_STRICT}; push @more_ssh_args, '-v' if $Config{ZM_UPLOAD_DEBUG}; $sftpOptions{more} = [@more_ssh_args]; my $sftp = Net::SFTP::Foreign->new($Config{ZM_UPLOAD_HOST}, %sftpOptions); if ( $sftp->error ) { Error("Unable to create SFTP connection: ".$sftp->error); return 0; } $sftp->setcwd($Config{ZM_UPLOAD_REM_DIR}) or Error("SFTP - Unable to setcwd: ".$sftp->error) if $Config{ZM_UPLOAD_REM_DIR}; $sftp->put($archLocPath, $archFile) or Error("SFTP - Unable to upload '$archLocPath': ".$sftp->error); } unlink($archLocPath); my $sql = 'UPDATE Events SET Uploaded = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Event->{Id}) or Fatal("Unable to execute '$sql': ".$dbh->errstr()); } return 1; } # end sub uploadArchFile sub substituteTags { my $text = shift; my $filter = shift; my $Event = shift; my $attachments_ref = shift; # First we'd better check what we need to get # We have a filter and an event, do we need any more # monitor information? my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; my $Monitor = $Event->Monitor() if $need_monitor; # Do we need the image information too? my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA)%/; my $first_alarm_frame; my $max_alarm_frame; my $max_alarm_score = 0; if ( $need_images ) { my $sql = q`SELECT * FROM Frames WHERE EventId = ? AND Type = 'Alarm' ORDER BY FrameId`; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Event->{Id}) or Fatal("Unable to execute '$sql': ".$dbh->errstr()); my $rows = 0; while( my $frame = $sth->fetchrow_hashref() ) { if ( !$first_alarm_frame ) { $first_alarm_frame = $frame; } if ( $frame->{Score} > $max_alarm_score ) { $max_alarm_frame = $frame; $max_alarm_score = $frame->{Score}; } $rows ++; } Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alaarm_frame: $max_alarm_frame, score: $max_alarm_score"); $sth->finish(); } my $url = $Config{ZM_URL}; $text =~ s/%ZP%/$url/g; $text =~ s/%MN%/$Event->{MonitorName}/g; $text =~ s/%MET%/$Monitor->{TotalEvents}/g; $text =~ s/%MEH%/$Monitor->{HourEvents}/g; $text =~ s/%MED%/$Monitor->{DayEvents}/g; $text =~ s/%MEW%/$Monitor->{WeekEvents}/g; $text =~ s/%MEM%/$Monitor->{MonthEvents}/g; $text =~ s/%MEA%/$Monitor->{ArchivedEvents}/g; $text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g; $text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g; $text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g; $text =~ s/%EP%/$url?view=event&mid=$Event->{MonitorId}&eid=$Event->{Id}/g; $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$Event->{MonitorId}&eid=$Event->{Id}/g; $text =~ s/%EPI%/$url?view=event&mode=still&mid=$Event->{MonitorId}&eid=$Event->{Id}/g; $text =~ s/%EI%/$Event->{Id}/g; $text =~ s/%EN%/$Event->{Name}/g; $text =~ s/%EC%/$Event->{Cause}/g; $text =~ s/%ED%/$Event->{Notes}/g; $text =~ s/%ET%/$Event->{StartTime}/g; $text =~ s/%EL%/$Event->{Length}/g; $text =~ s/%EF%/$Event->{Frames}/g; $text =~ s/%EFA%/$Event->{AlarmFrames}/g; $text =~ s/%EST%/$Event->{TotScore}/g; $text =~ s/%ESA%/$Event->{AvgScore}/g; $text =~ s/%ESM%/$Event->{MaxScore}/g; if ( $first_alarm_frame ) { $text =~ s/%EPI1%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g; $text =~ s/%EPIM%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g; if ( $attachments_ref && $text =~ s/%EI1%//g ) { my $path = generateImage($Event, $first_alarm_frame); if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } } if ( $attachments_ref && ( $text =~ s/%EIM%//g ) ) { # Don't attach the same image twice if ( !@$attachments_ref || ( $first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) ) { my $path = generateImage($Event, $max_alarm_frame); if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { Warning("No image for EIM"); } } } if ( $attachments_ref && $text =~ s/%EI1A%//g ) { my $path = generateImage($Event, $first_alarm_frame, 'analyse'); if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { Warning("No image for EI1A"); } } if ( $attachments_ref && $text =~ s/%EIMA%//g ) { # Don't attach the same image twice if ( !@$attachments_ref || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId}) ) { my $path = generateImage($Event, $max_alarm_frame, 'analyse'); if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { Warning('No image for EIMA'); } } } } # end if $first_alarm_frame if ( $attachments_ref ) { if ( $text =~ s/%EV%//g ) { if ( $$Event{DefaultVideo} ) { push @$attachments_ref, { type=>'video/mp4', path=>join('/',$Event->Path(), $Event->DefaultVideo()) }; } elsif ( $Config{ZM_OPT_FFMPEG} ) { my ( $format, $path ) = generateVideo($filter, $Event); if ( !$format ) { return undef; } push( @$attachments_ref, { type=>"video/$format", path=>$path } ); } } if ( $text =~ s/%EVM%//g ) { my ( $format, $path ) = generateVideo($filter, $Event, 1); if ( !$format ) { return undef; } push @$attachments_ref, { type=>"video/$format", path=>$path }; } } $text =~ s/%FN%/$filter->{Name}/g; ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; $text =~ s/%FP%/$url?view=filter&mid=$Event->{MonitorId}&filter_name=$filter_name/g; return $text; } # end subsitituteTags sub sendEmail { my $filter = shift; my $Event = shift; if ( ! $Config{ZM_FROM_EMAIL} ) { Error('No from email address defined, not sending email'); return 0; } if ( ! $Config{ZM_EMAIL_ADDRESS} ) { Error('No email address defined, not sending email'); return 0; } Info('Creating notification email'); my $subject = substituteTags($Config{ZM_EMAIL_SUBJECT}, $filter, $Event); return 0 if !$subject; my @attachments; my $body = substituteTags($Config{ZM_EMAIL_BODY}, $filter, $Event, \@attachments); return 0 if !$body; Info("Sending notification email '$subject'"); eval { if ( $Config{ZM_NEW_MAIL_MODULES} ) { ### Create the multipart container my $mail = MIME::Lite->new ( From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_EMAIL_ADDRESS}, Subject => $subject, Type => 'multipart/mixed' ); ### Add the text message part $mail->attach ( Type => 'TEXT', Data => $body ); ### Add the attachments foreach my $attachment ( @attachments ) { Info( "Attaching '$attachment->{path}'" ); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, Disposition => 'attachment' ); } ### Send the Message if ( $Config{ZM_SSMTP_MAIL} ) { my $ssmtp_location = $Config{ZM_SSMTP_PATH}; if ( !$ssmtp_location ) { if ( logDebugging() ) { Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message"); } $ssmtp_location = qx('which ssmtp'); } if ( !$ssmtp_location ) { Debug('Unable to find ssmtp, trying MIME::Lite->send'); MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } else { ### Send using SSMTP $mail->send('sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS}); } } else { MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } } else { my $mail = MIME::Entity->build( From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_EMAIL_ADDRESS}, Subject => $subject, Type => (($body=~//)?'text/html':'text/plain'), Data => $body ); foreach my $attachment ( @attachments ) { Info("Attaching '$attachment->{path}'"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, Encoding => 'base64' ); } $mail->smtpsend(Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL}); } }; if ( $@ ) { Error("Unable to send email: $@"); return 0; } else { Info('Notification email sent'); } my $sql = 'UPDATE Events SET Emailed = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Event->{Id}) or Fatal("Unable to execute '$sql': ".$dbh->errstr()); return 1; } sub sendMessage { my $filter = shift; my $Event = shift; if ( ! $Config{ZM_FROM_EMAIL} ) { Error('No from email address defined, not sending message'); return 0; } if ( ! $Config{ZM_MESSAGE_ADDRESS} ) { Error('No message address defined, not sending message'); return 0; } Info('Creating notification message'); my $subject = substituteTags($Config{ZM_MESSAGE_SUBJECT}, $filter, $Event); return 0 if !$subject; my @attachments; my $body = substituteTags($Config{ZM_MESSAGE_BODY}, $filter, $Event, \@attachments); return 0 if !$body; Info("Sending notification message '$subject'"); eval { if ( $Config{ZM_NEW_MAIL_MODULES} ) { ### Create the multipart container my $mail = MIME::Lite->new( From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_MESSAGE_ADDRESS}, Subject => $subject, Type => 'multipart/mixed' ); ### Add the text message part $mail->attach ( Type => 'TEXT', Data => $body ); ### Add the attachments foreach my $attachment ( @attachments ) { Info("Attaching '$attachment->{path}"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, Disposition => 'attachment' ); } ### Send the Message if ( $Config{ZM_SSMTP_MAIL} ) { my $ssmtp_location = $Config{ZM_SSMTP_PATH}; if ( !$ssmtp_location ) { if ( logDebugging() ) { Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message"); } $ssmtp_location = qx('which ssmtp'); } if ( !$ssmtp_location ) { Debug('Unable to find ssmtp, trying MIME::Lite->send'); MIME::Lite->send(smtp=>$Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } else { ### Send using SSMTP $mail->send('sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS}); } } else { MIME::Lite->send(smtp=>$Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } } else { my $mail = MIME::Entity->build( From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_MESSAGE_ADDRESS}, Subject => $subject, Type => (($body=~//)?'text/html':'text/plain'), Data => $body ); foreach my $attachment ( @attachments ) { Info("Attaching '$attachment->{path}'"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, Encoding => 'base64' ); } $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); } }; if ( $@ ) { Error("Unable to send email: $@"); return 0; } else { Info('Notification message sent'); } my $sql = 'UPDATE Events SET Messaged = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Event->{Id}) or Fatal("Unable to execute '$sql': ".$dbh->errstr()); return 1; } # end sub sendMessage sub executeCommand { my $filter = shift; my $Event = shift; my $event_path = $Event->Path(); my $command = $filter->{AutoExecuteCmd}; $command .= " $event_path"; $command = substituteTags($command, $filter, $Event); Info("Executing '$command'"); my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { chomp($output); Debug("Output: $output"); } if ( $status ) { Error("Command '$command' exited with status: $status"); return 0; } else { my $sql = 'UPDATE Events SET Executed = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute( $Event->{Id} ) or Fatal("Unable to execute '$sql': ".$dbh->errstr()); } return( 1 ); } ZoneMinder-1.32.2/scripts/zmdc.pl.in0000644000000000000000000006074113365153155015754 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder Daemon Control Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmdc.pl - ZoneMinder Daemon Control script =head1 SYNOPSIS zmdc.pl {command} [daemon [options]] =head1 DESCRIPTION This script is the gateway for controlling the various ZoneMinder daemons. All starting, stopping and restarting goes through here. On the first invocation it starts up a server which subsequently records what's running and what's not. Other invocations just connect to the server and pass instructions to it. =head1 OPTIONS {command} - One of 'startup|shutdown|status|check|logrot' or 'start|stop|restart|reload|version'. [daemon [options]] - Daemon name and options, required for second group of commands =cut use strict; use warnings; use bytes; # ========================================================================== # # User config # # ========================================================================== # in useconds, not seconds. use constant MAX_CONNECT_DELAY => 40; # ========================================================================== # # Don't change anything from here on down # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use POSIX; use Socket; use IO::Handle; use Time::HiRes qw(usleep); use autouse 'Pod::Usage'=>qw(pod2usage); #use Data::Dumper; use constant SOCK_FILE => $Config{ZM_PATH_SOCKS}.'/zmdc'.($Config{ZM_SERVER_ID}?$Config{ZM_SERVER_ID}:'').'.sock'; $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; if ( $Config{ZM_LD_PRELOAD} ) { Debug("Adding ENV{LD_PRELOAD} = $Config{ZM_LD_PRELOAD}"); $ENV{LD_PRELOAD} = $Config{ZM_LD_PRELOAD}; foreach my $lib ( split(/\s+/, $ENV{LD_PRELOAD} ) ) { if ( ! -e $lib ) { Warning("LD_PRELOAD lib $lib does not exist from LD_PRELOAD $ENV{LD_PRELOAD}."); } } } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', 'zma', 'zmfilter.pl', 'zmaudit.pl', 'zmtrigger.pl', 'zmx10.pl', 'zmwatch.pl', 'zmupdate.pl', 'zmstats.pl', 'zmtrack.pl', 'zmtelemetry.pl' ); if ( $Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { push @daemons,'zmeventnotification.pl'; } my $command = shift @ARGV; if ( !$command ) { print(STDERR "No command given\n"); pod2usage(-exitstatus => -1); } if ( $command eq 'version' ) { print ZoneMinder::Base::ZM_VERSION."\n"; exit(0); } my $needs_daemon = $command !~ /(?:startup|shutdown|status|check|logrot|version)/; my $daemon = shift @ARGV; if ( $needs_daemon && !$daemon ) { print(STDERR "No daemon given\n"); pod2usage(-exitstatus => -1); } my @args; my $daemon_patt = '('.join('|', @daemons).')'; if ( $needs_daemon ) { if ( $daemon =~ /^${daemon_patt}$/ ) { $daemon = $1; } else { print(STDERR "Invalid daemon '$daemon' specified"); pod2usage(-exitstatus => -1); } } foreach my $arg ( @ARGV ) { # Detaint arguments, if they look ok #if ( $arg =~ /^(-{0,2}[\w]+)/ ) if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) { push @args, $1; } else { print(STDERR "Bogus argument '$arg' found"); exit(-1); } } my $dbh = zmDbConnect(); socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); my $saddr = sockaddr_un(SOCK_FILE); my $server_up = connect(CLIENT, $saddr); if ( !$server_up ) { if ( $Config{ZM_SERVER_ID} ) { use Sys::MemInfo qw(totalmem freemem totalswap freeswap); use ZoneMinder::Server qw(CpuLoad); if ( ! defined $dbh->do(q{UPDATE Servers SET Status=?,TotalMem=?,FreeMem=?,TotalSwap=?,FreeSwap=? WHERE Id=?}, undef, 'NotRunning', &totalmem, &freemem, &totalswap, &freeswap, $Config{ZM_SERVER_ID} ) ) { Error("Failed Updating status of Server record to Not RUnning for Id=$Config{ZM_SERVER_ID}" . $dbh->errstr()); } } # Server is not up. Some commands can still be handled if ( $command eq 'logrot' ) { # If server is not running, then logrotate doesn't need to do anything. exit(); } if ( $command eq 'check' ) { print("stopped\n"); exit(); } elsif ( $command ne 'startup' ) { print("Unable to connect to server using socket at " . SOCK_FILE . "\n"); exit(-1); } # The server isn't there print("Starting server\n"); close(CLIENT); if ( my $cpid = fork() ) { # Parent process just sleep and fall through # I'm still not sure why we need to re-init the logs logInit(); socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); my $attempts = 0; while( !connect(CLIENT, $saddr) ) { $attempts++; Debug("Waiting for zmdc.pl server process at ".SOCK_FILE.", attempt $attempts"); Fatal("Can't connect: $!") if $attempts > MAX_CONNECT_DELAY; usleep(200000); } # end while } elsif ( defined($cpid) ) { ZMServer::run(); } else { Fatal("Can't fork: $!"); } } # end if ! server is up if ( ($command eq 'check') && !$daemon ) { print("running\n"); exit(); } elsif ( $command eq 'startup' ) { # Our work here is done exit() if !$server_up; } # The server is there, connect to it CLIENT->autoflush(); my $message = join(';', $command, ( $daemon ? $daemon : () ), @args); print(CLIENT $message); shutdown(CLIENT, 1); while( my $line = ) { chomp($line); print("$line\n"); } close(CLIENT); exit; package ZMServer; use strict; use warnings; use bytes; @EXTRA_PERL_LIB@ use ZoneMinder; use POSIX; use Socket; use IO::Handle; use Time::HiRes qw(usleep); use Sys::MemInfo qw(totalmem freemem totalswap freeswap); use ZoneMinder::Server qw(CpuLoad); #use Data::Dumper; use constant KILL_DELAY => 60; # seconds to wait between sending TERM and sending KILL our %cmd_hash; our %pid_hash; our %terminating_processes; our %pids_to_reap; our $zm_terminate = 0; sub run { # Call this first otherwise stdout/stderror redirects to the pidfile = bad if ( open(my $PID, '>', ZM_PID) ) { print($PID $$); close($PID); } else { # Log not initialized at this point so use die instead die "Can't open pid file at ".ZM_PID."\n"; } my $fd = 0; while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { POSIX::close($fd++); } setpgrp(); # dbh got closed with the rest of the fd's above, so need to reconnect. my $dbh = zmDbConnect(1); logInit(); dPrint(ZoneMinder::Logger::INFO, 'Server starting at ' .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); # We don't want to leave killall zombies, so ignore SIGCHLD $SIG{CHLD} = 'IGNORE'; # Tell any existing processes to die, wait 1 second between TERM and KILL killAll(1); dPrint(ZoneMinder::Logger::INFO, 'Socket should be open at ' .main::SOCK_FILE); socket(SERVER, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); unlink(main::SOCK_FILE) or Error('Unable to unlink ' . main::SOCK_FILE .". Error message was: $!") if -e main::SOCK_FILE; bind(SERVER, $saddr) or Fatal("Can't bind to " . main::SOCK_FILE . ": $!"); listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); $SIG{CHLD} = \&chld_sig_handler; $SIG{INT} = \&shutdown_sig_handler; $SIG{TERM} = \&shutdown_sig_handler; $SIG{ABRT} = \&shutdown_sig_handler; $SIG{HUP} = \&logrot; my $rin = ''; vec($rin, fileno(SERVER), 1) = 1; my $win = $rin; my $ein = $win; my $timeout = 1; my $secs_count = 0; while( !$zm_terminate ) { if ( $Config{ZM_SERVER_ID} ) { if ( ! ( $secs_count % 60 ) ) { Debug("Connecting"); while ( (!$zm_terminate) and !($dbh and $dbh->ping()) ) { Warning("Not connected to db ($dbh)".($dbh?' ping('.$dbh->ping().')':''). ($DBI::errstr?" errstr($DBI::errstr)":'').' Reconnecting'); $dbh = zmDbConnect(); } my @cpuload = CpuLoad(); Debug("Updating Server record @cpuload"); if ( ! defined $dbh->do(q{UPDATE Servers SET Status=?,CpuLoad=?,TotalMem=?,FreeMem=?,TotalSwap=?,FreeSwap=? WHERE Id=?}, undef, 'Running', $cpuload[0], &totalmem, &freemem, &totalswap, &freeswap, $Config{ZM_SERVER_ID} ) ) { Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID}".$dbh->errstr()); } } $secs_count += 1; } my $nfound = select(my $rout = $rin, undef, undef, $timeout); if ( $nfound > 0 ) { if ( vec($rout, fileno(SERVER), 1) ) { my $paddr = accept(CLIENT, SERVER); my $message = ; next if !$message; my ( $command, $daemon, @args ) = split(';', $message); if ( $command eq 'start' ) { start($daemon, @args); } elsif ( $command eq 'stop' ) { stop($daemon, @args); } elsif ( $command eq 'restart' ) { restart($daemon, @args); } elsif ( $command eq 'reload' ) { reload($daemon, @args); } elsif ( $command eq 'startup' ) { # Do nothing, this is all we're here for dPrint(ZoneMinder::Logger::WARNING, "Already running, ignoring command '$command'\n"); } elsif ( $command eq 'shutdown' ) { # Break out of while loop last; } elsif ( $command eq 'check' ) { check($daemon, @args); } elsif ( $command eq 'status' ) { if ( $daemon ) { status($daemon, @args); } else { status(); } } elsif ( $command eq 'logrot' ) { logrot(); } else { dPrint(ZoneMinder::Logger::ERROR, "Invalid command '$command'\n"); } close(CLIENT); } else { Error('Bogus descriptor'); } } elsif ( $nfound < 0 ) { if ( $! == EINTR ) { # Dead child, will be reaped #print( "Probable dead child\n" ); # See if it needs to start up again } elsif ( $! == EPIPE ) { Error("Can't select: $!"); } else { Fatal("Can't select: $!"); } } else { #print( "Select timed out\n" ); } restartPending(); check_for_processes_to_kill() if %terminating_processes; reaper() if %pids_to_reap; } # end while dPrint(ZoneMinder::Logger::INFO, 'Server exiting at ' .strftime( '%y/%m/%d %H:%M:%S', localtime() ) ."\n" ); if ( $Config{ZM_SERVER_ID} ) { $dbh = zmDbConnect() if ! ($dbh and $dbh->ping()); if ( ! defined $dbh->do(q{UPDATE Servers SET Status='NotRunning' WHERE Id=?}, undef, $Config{ZM_SERVER_ID}) ) { Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID}".$dbh->errstr()); } } shutdownAll(); } sub cPrint { # One thought here, if no client exists to read these... does it block? if ( fileno(CLIENT) ) { print CLIENT @_ } } # I think the purpose of this is to echo the logs to the client process so it can then display them. sub dPrint { my $logLevel = shift; cPrint(@_); if ( $logLevel == ZoneMinder::Logger::DEBUG ) { Debug(@_); } elsif ( $logLevel == ZoneMinder::Logger::INFO ) { Info(@_); } elsif ( $logLevel == ZoneMinder::Logger::WARNING ) { Warning(@_); } elsif ( $logLevel == ZoneMinder::Logger::ERROR ) { Error(@_); } elsif ( $logLevel == ZoneMinder::Logger::FATAL ) { Fatal(@_); } } sub start { my $daemon = shift; my @args = @_; my $command = join(' ', $daemon, @args); my $process = $cmd_hash{$command}; if ( !$process ) { # It's not running, or at least it's not been started by us $process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef }; } elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) { dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .", pid = $process->{pid}\n" ); return; } my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); sigprocmask(SIG_BLOCK, $blockset, $sigset) or Fatal("Can't block SIGCHLD: $!"); # Apparently the child closing the db connection can affect the parent. zmDbDisconnect(); if ( my $cpid = fork() ) { $dbh = zmDbConnect(1); # This logReinit is required. Not sure why. #logReinit(); $process->{pid} = $cpid; $process->{started} = time(); delete $process->{pending}; dPrint(ZoneMinder::Logger::INFO, "'$command' starting at " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .", pid = $process->{pid}\n" ); $cmd_hash{$process->{command}} = $pid_hash{$cpid} = $process; sigprocmask(SIG_SETMASK, $sigset) or Fatal("Can't restore SIGCHLD: $!"); } elsif ( defined($cpid) ) { # Child process # Force reconnection to the db. $dbh got copied, but isn't really valid anymore. $dbh = zmDbConnect(1); logReinit(); dPrint(ZoneMinder::Logger::INFO, "'$command' started at " .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); if ( $daemon =~ /^${daemon_patt}$/ ) { $daemon = $Config{ZM_PATH_BIN}.'/'.$1; } else { Fatal("Invalid daemon '$daemon' specified"); } my @good_args; foreach my $arg ( @args ) { # Detaint arguments, if they look ok if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) { push @good_args, $1; } else { Fatal("Bogus argument '$arg' found"); } } logTerm(); zmDbDisconnect(); my $fd = 3; # leave stdin,stdout,stderr open. Closing them causes problems with libx264 while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { POSIX::close($fd++); } $SIG{CHLD} = 'DEFAULT'; $SIG{HUP} = 'DEFAULT'; $SIG{INT} = 'DEFAULT'; $SIG{TERM} = 'DEFAULT'; $SIG{ABRT} = 'DEFAULT'; exec($daemon, @good_args) or Fatal("Can't exec: $!"); } else { Fatal("Can't fork: $!"); } } # end sub start # Sends the stop signal, without waiting around to see if the process died. sub send_stop { my ( $final, $process ) = @_; my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); sigprocmask(SIG_BLOCK, $blockset, $sigset) or die "dying at block...\n"; my $command = $process->{command}; if ( $process->{pending} ) { delete $cmd_hash{$command}; dPrint(ZoneMinder::Logger::INFO, "Command '$command' removed from pending list at " .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; return(); } my $pid = $process->{pid}; if ( !$pid ) { dPrint(ZoneMinder::Logger::ERROR, "No process with command of '$command' is running\n"); sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; return(); } if ( !$pid_hash{$pid} ) { dPrint(ZoneMinder::Logger::ERROR, "No process with command of '$command' pid $pid is running\n"); sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; return(); } dPrint(ZoneMinder::Logger::INFO, "'$command' sending stop to pid $pid at " .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); $process->{keepalive} = !$final; $process->{term_sent_at} = time if ! $process->{term_sent_at}; $process->{pending} = 0; $terminating_processes{$command} = $process; kill('TERM', $pid); sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; return $pid; } # end sub send_stop sub check_for_processes_to_kill { # Turn off SIGCHLD my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); sigprocmask(SIG_BLOCK, $blockset, $sigset) or die "dying at block...\n"; foreach my $command ( keys %terminating_processes ) { my $process = $cmd_hash{$command}; if ( ! $process ) { Debug("No process found for $command"); delete $terminating_processes{$command}; next; } if ( ! $$process{pid} ) { Warning("Have no pid for $command."); delete $terminating_processes{$command}; next; } my $now = time; Debug("Have process $command at pid $$process{pid} $now - $$process{term_sent_at} = " . ( $now - $$process{term_sent_at} )); if ( $$process{term_sent_at} and ( $now - $$process{term_sent_at} > KILL_DELAY ) ) { dPrint(ZoneMinder::Logger::WARNING, "'$$process{command}' has not stopped at " .strftime('%y/%m/%d %H:%M:%S', localtime()) .' after ' . KILL_DELAY . ' seconds.' ." Sending KILL to pid $$process{pid}\n" ); kill('KILL', $$process{pid}); delete $terminating_processes{$command}; } } sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; } # end sub check_for_processess_to_kill sub stop { my ( $daemon, @args ) = @_; my $command = join(' ', $daemon, @args ); my $process = $cmd_hash{$command}; if ( !$process ) { dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'"); return; } send_stop(1, $process); } # restart is the same as stop, except that we flag the processes for restarting once it dies # One difference is that if we don't know about the process, then we start it. sub restart { my ( $daemon, @args ) = @_; my $command = join(' ', $daemon, @args); dPrint(ZoneMinder::Logger::DEBUG, "Restarting $command\n"); my $process = $cmd_hash{$command}; if ( !$process ) { dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'\n"); start($daemon, @args); return; } # Start will be handled by the reaper send_stop(0, $process); return; } sub reload { my $daemon = shift; my @args = @_; my $command = join(' ', $daemon, @args); my $process = $cmd_hash{$command}; if ( $process ) { if ( $process->{pid} ) { kill('HUP', $process->{pid}); } } } sub logrot { logReinit(); foreach my $process ( values %pid_hash ) { if ( $process->{pid} ) { # && $process->{command} =~ /^zm.*\.pl/ ) { kill('HUP', $process->{pid}); } } } sub shutdown_sig_handler { $zm_terminate = 1; } sub chld_sig_handler { my $saved_status = $!; # Wait for a child to terminate while ( (my $cpid = waitpid(-1, WNOHANG)) > 0 ) { $pids_to_reap{$cpid} = { status=>$?, stopped=>time() }; } # end while waitpid $SIG{CHLD} = \&chld_sig_handler; $! = $saved_status; } sub reaper { foreach my $cpid ( keys %pids_to_reap ) { my $process = $pid_hash{$cpid}; delete $pid_hash{$cpid}; my $reap_info = $pids_to_reap{$cpid}; my ( $status, $stopped ) = @$reap_info{'status','stopped'}; delete $pids_to_reap{$cpid}; if ( !$process ) { dPrint(ZoneMinder::Logger::INFO, "Can't find child with pid of '$cpid'\n"); next; } delete $terminating_processes{$$process{command}}; delete $$process{term_sent_at}; $process->{stopped} = $stopped; $process->{runtime} = ($process->{stopped}-$process->{started}); delete $process->{pid}; my $exit_status = $status>>8; my $exit_signal = $status&0xfe; my $core_dumped = $status&0x01; my $out_str = "'$process->{command}' "; if ( $exit_signal ) { # 15 == TERM, 14 == ALARM if ( $exit_signal == 15 || $exit_signal == 14 ) { $out_str .= 'exited'; } else { $out_str .= 'crashed'; } $out_str .= ", signal $exit_signal"; } else { $out_str .= 'exited '; if ( $exit_status ) { $out_str .= "abnormally, exit status $exit_status"; } else { $out_str .= 'normally'; } } #print( ", core dumped" ) if ( $core_dumped ); $out_str .= "\n"; if ( $exit_status == 0 ) { Info($out_str); } else { Error($out_str); } if ( $process->{keepalive} ) { # Schedule for immediate restart $cmd_hash{$process->{command}} = $process; if ( !$process->{delay} || ($process->{runtime} > $Config{ZM_MAX_RESTART_DELAY} ) ) { #start( $process->{daemon}, @{$process->{args}} ); $process->{pending} = $process->{stopped}; $process->{delay} = 5; } else { $process->{pending} = $process->{stopped}+$process->{delay}; $process->{delay} *= 2; # Limit the start delay to 15 minutes max if ( $process->{delay} > $Config{ZM_MAX_RESTART_DELAY} ) { $process->{delay} = $Config{ZM_MAX_RESTART_DELAY}; } } Debug("Delay for $$process{command} is now $$process{delay}"); } else { delete $cmd_hash{$$process{command}}; } } # end foreach pid_to_reap } # end sub reaper sub restartPending { # Restart any pending processes, we list them first because cmd_hash may change in foreach my @processes = values %cmd_hash; foreach my $process ( @processes ) { if ( $process->{pending} && $process->{pending} <= time() ) { dPrint(ZoneMinder::Logger::INFO, "Starting pending process, $process->{command}\n"); start($process->{daemon}, @{$process->{args}}); } } } sub shutdownAll { foreach my $pid ( keys %pid_hash ) { # This is a quick fix because a SIGCHLD can happen and alter pid_hash while we are in here. next if ! $pid_hash{$pid}; send_stop(1, $pid_hash{$pid}); } while ( keys %terminating_processes ) { reaper() if %pids_to_reap; check_for_processes_to_kill(); if ( %terminating_processes ) { Debug("Still " . %terminating_processes . ' to die. sleeping'); sleep(1); } } dPrint(ZoneMinder::Logger::INFO, 'Server shutdown at ' .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); unlink(main::SOCK_FILE) or Error("Unable to unlink " . main::SOCK_FILE .". Error message was: $!") if ( -e main::SOCK_FILE ); unlink(ZM_PID) or Error("Unable to unlink " . ZM_PID .". Error message was: $!") if ( -e ZM_PID ); close(CLIENT); close(SERVER); exit(); } sub check { my $daemon = shift; my @args = @_; my $command = join(' ', $daemon, @args); my $process = $cmd_hash{$command}; if ( !$process ) { cPrint("unknown\n"); } elsif ( $process->{pending} ) { cPrint("pending\n"); } else { my $cpid = $process->{pid}; if ( ! $pid_hash{$cpid} ) { cPrint("stopped\n"); } else { cPrint("running\n"); } } } sub status { my $daemon = shift; my @args = @_; if ( defined($daemon) ) { my $command = join(' ', $daemon, @args); my $process = $cmd_hash{$command}; if ( ! $process ) { dPrint(ZoneMinder::Logger::DEBUG, "'$command' not running\n"); return; } if ( $process->{pending} ) { dPrint(ZoneMinder::Logger::DEBUG, "'$command' pending at " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{pending})) ."\n" ); } else { my $pid = $process->{pid}; if ( ! $pid_hash{$pid} ) { dPrint(ZoneMinder::Logger::DEBUG, "'$command' not running\n"); return; } } dPrint(ZoneMinder::Logger::DEBUG, "'$command' running since " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .", pid = $process->{pid}" ); } else { foreach my $process ( values %pid_hash ) { my $out_str = "'$process->{command}' running since " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .", pid = $process->{pid}" ; $out_str .= ", valid" if ( kill(0, $process->{pid}) ); $out_str .= "\n"; dPrint(ZoneMinder::Logger::DEBUG, $out_str); } foreach my $process ( values %cmd_hash ) { if ( $process->{pending} ) { dPrint(ZoneMinder::Logger::DEBUG, "'$process->{command}' pending at " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{pending})) ."\n" ); } } # end foreach process } } # end sub status sub killAll { my $delay = shift; # Why sleep before sending term? #sleep( $delay ); my $killall; if ( '@HOST_OS@' eq 'BSD' ) { $killall = 'killall -q -'; } elsif ( '@HOST_OS@' eq 'solaris' ) { $killall = 'pkill -'; } else { $killall = 'killall -q -s '; } foreach my $daemon ( @daemons ) { my $cmd = $killall ."TERM $daemon"; Debug($cmd); qx($cmd); } sleep($delay); foreach my $daemon ( @daemons ) { my $cmd = $killall."KILL $daemon"; Debug($cmd); qx($cmd); } } 1; __END__ ZoneMinder-1.32.2/scripts/zmeventdump.in0000644000000000000000000000336513365153155016762 0ustar rootroot#!/bin/bash #=============================================================================== # # FILE: zmeventdump # # USAGE: ./zmeventdump # # DESCRIPTION: Uses mysqldump to create a .sql file for individual zm # events to make Event table recovery possible by doing a # 'find' search in ZoneMinder the events directory # # OPTIONS: --- None # REQUIREMENTS: --- mysqldump # BUGS: --- # NOTES: --- # AUTHOR: Ross Melin # COMPANY: # VERSION: 3.0 # CREATED: 02/27/2008 05:39:00 PM PST # REVISION: --- Update for changed zmfilter and # ZM_USE_DEEP_STORAGE #=============================================================================== # Edit these to suit your configuration ZM_CONFIG=@ZM_CONFIG@ # ZM_VERSION in the config is now deprecated but will likely still exist in people's config files. This will override it. ZM_VERSION=@VERSION@ MYSQLDUMP=/usr/bin/mysqldump # The rest should not need editing # Get the mysql user and password source $ZM_CONFIG # zmfilter now passes the full path as an argument EVENT_PATH=$1 # Get the event id from a filename in the event directory EVENT_ID=$(ls $1/.[0-9]* | sed s:$1\/\.::) MYDUMPOPTS="--user=$ZM_DB_USER --password=$ZM_DB_PASS --skip-opt --compact --quick --no-create-info" # Dump the sql statements needed to reload the Events, Frames and Stats tables echo "-- ZM_DB_VERSION=$ZM_VERSION " > $EVENT_PATH/.sql $MYSQLDUMP $MYDUMPOPTS --where="Id=$EVENT_ID" zm Events >> $EVENT_PATH/.sql $MYSQLDUMP $MYDUMPOPTS --where="Eventid=$EVENT_ID" zm Frames >> $EVENT_PATH/.sql $MYSQLDUMP $MYDUMPOPTS --where="Eventid=$EVENT_ID" zm Stats >> $EVENT_PATH/.sql exit 0 ZoneMinder-1.32.2/scripts/ZoneMinder/0000755000000000000000000000000013365153155016117 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/Changes0000644000000000000000000000024513365153155017413 0ustar rootrootRevision history for Perl extension ZoneMinder. 0.01 Thu Dec 15 17:22:29 2005 - original version; created by h2xs 1.23 with options -XA -b 5.6.0 -n ZoneMinder ZoneMinder-1.32.2/scripts/ZoneMinder/README0000644000000000000000000000223013365153155016774 0ustar rootrootZoneMinder version 0.01 ======================= The README is used to introduce the module and provide instructions on how to install the module, any machine dependencies it may have (for example C compilers and installed libraries) and any other information that should be provided before the module is installed. A README file is required for CPAN modules since CPAN extracts the README file from a module distribution so that people browsing the archive can use it get an idea of the modules uses. It is usually a good idea to provide version information here so that people can decide whether fixes for the module are worth downloading. INSTALLATION To install this module type the following: perl Makefile.PL make make test make install DEPENDENCIES This module requires these other modules and libraries: blah blah blah COPYRIGHT AND LICENCE Put the correct copyright and licence information here. Copyright (C) 2005 by Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. ZoneMinder-1.32.2/scripts/ZoneMinder/lib/0000755000000000000000000000000013365153155016665 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder.pm0000644000000000000000000000722413365153155021302 0ustar rootroot# ========================================================================== # # ZoneMinder Common Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder; use 5.006; use strict; use warnings; require Exporter; use ZoneMinder::Base qw(:all); use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::General qw(:all); use ZoneMinder::Database qw(:all); use ZoneMinder::Memory qw(:all); our @ISA = qw( Exporter ZoneMinder::Base ZoneMinder::Config ZoneMinder::Logger ZoneMinder::General ZoneMinder::Database ZoneMinder::Memory ); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'base' => [ @ZoneMinder::Base::EXPORT_OK ], 'config' => [ @ZoneMinder::Config::EXPORT_OK ], 'debug' => [ @ZoneMinder::Logger::EXPORT_OK ], 'general' => [ @ZoneMinder::General::EXPORT_OK ], 'database' => [ @ZoneMinder::Database::EXPORT_OK ], 'memory' => [ @ZoneMinder::Memory::EXPORT_OK ], ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = @{ $EXPORT_TAGS{'all'} }; our @EXPORT = ( @EXPORT_OK ); our $VERSION = $ZoneMinder::Base::VERSION; 1; __END__ =head1 NAME ZoneMinder - Container module for common ZoneMinder modules =head1 SYNOPSIS use ZoneMinder; =head1 DESCRIPTION This module is a convenience container module that uses the ZoneMinder::Base, ZoneMinder::Common, ZoneMinder::Logger, ZoneMinder::Database and ZoneMinder::Memory modules. It also exports by default all symbols provided by the 'all' tag of each of the modules. Thus 'use'ing this module is equivalent to the following use ZoneMinder::Base qw(:all); use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); use ZoneMinder::Memory qw(:all); but is somewhat easier. =head2 EXPORT All symbols exported by the 'all' tag of each of the included modules. =head1 SEE ALSO ZoneMinder::Base, ZoneMinder::Common, ZoneMinder::Logger, ZoneMinder::Database, ZoneMinder::Memory http://www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2005 by Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/0000755000000000000000000000000013365153155020737 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in0000644000000000000000000002554713365153155023124 0ustar rootroot# ========================================================================== # # ZoneMinder Config Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Config; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; use ZoneMinder::ConfigData qw(:all); our @ISA = qw(Exporter ZoneMinder::Base); use vars qw( %Config ); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our @EXPORT_CONFIG = qw( %Config ); # Get populated by BEGIN our %EXPORT_TAGS = ( functions => [ qw( zmConfigLoad loadConfigFromDB saveConfigToDB ) ], constants => [ qw( ZM_PID ) ] ); push( @{$EXPORT_TAGS{config}}, @EXPORT_CONFIG ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; use constant ZM_PID => "@ZM_PID@"; # Path to the ZoneMinder run pid file use constant ZM_CONFIG => "@ZM_CONFIG@"; # Path to the ZoneMinder config file use constant ZM_CONFIG_SUBDIR => "@ZM_CONFIG_SUBDIR@"; # Path to the ZoneMinder config subfolder use Carp; # Load the config from the database into the symbol table BEGIN { # Process name, value pairs from the main config file first my $config_file = ZM_CONFIG; process_configfile($config_file); # Search for user created config files. If one or more are found then # update the Config hash with those values if ( -d ZM_CONFIG_SUBDIR ) { if ( -R ZM_CONFIG_SUBDIR ) { foreach my $filename ( glob ZM_CONFIG_SUBDIR."/*.conf" ) { process_configfile($filename); } } else { print( STDERR "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on ".ZM_CONFIG_SUBDIR.".\n" ); } } use DBI; my $socket; my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { $socket = ';mysql_socket='.$portOrSocket; } else { $socket = ';host='.$host.';port='.$portOrSocket; } } else { $socket = ';host='.$Config{ZM_DB_HOST}; } my $sslOptions = ''; if ( $Config{ZM_DB_SSL_CA_CERT} ) { $sslOptions = ';'.join(';', "mysql_ssl=1", "mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT}, "mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY}, "mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT} ); } my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} .$socket.$sslOptions , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ) or croak( "Can't connect to db" ); my $sql = 'SELECT Name,Value FROM Config'; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or croak( "Can't execute: ".$sth->errstr() ); while( my $config = $sth->fetchrow_hashref() ) { $Config{$config->{Name}} = $config->{Value}; } $sth->finish(); #$dbh->disconnect(); # if ( ! $Config{ZM_SERVER_ID} ) { $Config{ZM_SERVER_ID} = undef; $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); if ( $Config{ZM_SERVER_NAME} ) { $res = $sth->execute( $Config{ZM_SERVER_NAME} ); my $result = $sth->fetchrow_hashref(); $Config{ZM_SERVER_ID} = $$result{Id}; } elsif ( $Config{ZM_SERVER_HOST} ) { $res = $sth->execute( $Config{ZM_SERVER_HOST} ); my $result = $sth->fetchrow_hashref(); $Config{ZM_SERVER_ID} = $$result{Id}; } $sth->finish(); } # This subroutine must be inside the BEGIN block sub process_configfile { my $config_file = shift; if ( -R $config_file ) { open( my $CONFIG, '<', $config_file ) or croak( "Can't open config file '$config_file': $!" ); foreach my $str ( <$CONFIG> ) { next if ( $str =~ /^\s*$/ ); next if ( $str =~ /^\s*#/ ); my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/; if ( ! $name ) { print( STDERR "Warning, bad line in $config_file: $str\n" ); next; } # end if $name =~ tr/a-z/A-Z/; $Config{$name} = $value; } close( $CONFIG ); } else { print( STDERR "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $config_file\n" ); } } } # end BEGIN sub loadConfigFromDB { print( 'Loading config from DB' ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to load options from database: $DBI::errstr\n" ); return( 0 ); } my $sql = "select * from Config"; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or croak( "Can't execute: ".$sth->errstr() ); my $option_count = 0; while( my $config = $sth->fetchrow_hashref() ) { my ( $name, $value ) = ( $config->{Name}, $config->{Value} ); #print( "Name = '$name'\n" ); my $option = $options_hash{$name}; if ( !$option ) { warn( "No option '$name' found, removing.\n" ); next; } #next if ( $option->{category} eq 'hidden' ); if ( defined($value) ) { if ( $option->{type} == $types{boolean} ) { $option->{value} = $value?'yes':'no'; } else { $option->{value} = $value; } } $option_count++;; } $sth->finish(); print( " $option_count entries\n" ); return( $option_count ); } # end sub loadConfigFromDB sub saveConfigToDB { print( 'Saving config to DB ' . @options . " entries\n" ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to save options to database: $DBI::errstr\n" ); return( 0 ); } my $ac = $dbh->{AutoCommit}; $dbh->{AutoCommit} = 0; $dbh->do('LOCK TABLE Config WRITE') or croak( "Can't lock Config table: " . $dbh->errstr() ); my $sql = 'DELETE FROM Config'; my $res = $dbh->do( $sql ) or croak( "Can't do '$sql': ".$dbh->errstr() ); $sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Requires = ?"; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); foreach my $option ( @options ) { #next if ( $option->{category} eq 'hidden' ); #print( $option->{name}."\n" ) if ( !$option->{category} ); $option->{db_type} = $option->{type}->{db_type}; $option->{db_hint} = $option->{type}->{hint}; $option->{db_pattern} = $option->{type}->{pattern}; $option->{db_format} = $option->{type}->{format}; if ( $option->{db_type} eq 'boolean' ) { $option->{db_value} = ($option->{value} eq 'yes') ? '1' : '0'; } else { $option->{db_value} = $option->{value}; } if ( my $requires = $option->{requires} ) { $option->{db_requires} = join( ";", map { my $value = $_->{value}; $value = ($value eq 'yes') ? 1 : 0 if ( $options_hash{$_->{name}}->{db_type} eq 'boolean' ); ( "$_->{name}=$value" ) } @$requires ); } my $res = $sth->execute( $option->{id}, $option->{name}, $option->{db_value}, $option->{db_type}, $option->{default}, $option->{db_hint}, $option->{db_pattern}, $option->{db_format}, $option->{description}, $option->{help}, $option->{category}, $option->{readonly} ? 1 : 0, $option->{db_requires} ) or croak( "Can't execute: ".$sth->errstr() ); } # end foreach option $sth->finish(); $dbh->do('UNLOCK TABLES'); $dbh->{AutoCommit} = $ac; } # end sub saveConfigToDB 1; __END__ =head1 NAME ZoneMinder::Config - ZoneMinder configuration module. =head1 SYNOPSIS use ZoneMinder::Config qw(:all); =head1 DESCRIPTION The ZoneMinder::Config module is used to import the ZoneMinder configuration from the database. It will do this at compile time in a BEGIN block and require access to the zm.conf file either in the current directory or in its defined location in order to determine database access details, configuration from this file will also be included. If the :all or :config tags are used then this configuration is exported into the namespace of the calling program or module. Once the configuration has been imported then configuration variables are defined as constants and can be accessed directory by name, e.g. $lang = $Config{ZM_LANG_DEFAULT}; =head1 METHODS =over 4 =item loadConfigFromDB (); Loads existing configuration from the database (if any) and merges it with the definitions held in this module. This results in the merging of any new configuration and the removal of any deprecated configuration while preserving the existing values of every else. =item saveConfigToDB (); Saves configuration held in memory to the database. The act of loading and saving configuration is a convenient way to ensure that the configuration held in the database corresponds with the most recent definitions and that all components are using the same set of configuration. =head2 EXPORT None by default. The :constants tag will export the ZM_PID constant which details the location of the zm.pid file The :config tag will export all configuration from the database as well as any from the zm.conf file The :all tag will export all above symbols. =head1 SEE ALSO http://www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in0000644000000000000000000002471713365153155022576 0ustar rootroot# ========================================================================== # # ZoneMinder ONVIF Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::ONVIF; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter); our %EXPORT_TAGS = ( functions => [ qw( ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; use Data::UUID; use vars qw( $verbose $soap_version ); require ONVIF::Client; require WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort; require WSDiscovery10::Elements::Header; require WSDiscovery10::Elements::Types; require WSDiscovery10::Elements::Scopes; require WSDiscovery::TransportUDP; sub deserialize_message { my ($wsdl_client, $response) = @_; # copied and adapted from SOAP::WSDL::Client # get deserializer my $deserializer = $wsdl_client->get_deserializer(); if(! $deserializer) { $deserializer = SOAP::WSDL::Factory::Deserializer->get_deserializer({ soap_version => $wsdl_client->get_soap_version(), %{ $wsdl_client->get_deserializer_args() }, }); } # set class resolver if serializer supports it $deserializer->set_class_resolver( $wsdl_client->get_class_resolver() ) if ( $deserializer->can('set_class_resolver') ); # Try deserializing response - there may be some, # even if transport did not succeed (got a 500 response) if ( ! $response ) { return; } # as our faults are false, returning a success marker is the only # reliable way of determining whether the deserializer succeeded. # Custom deserializers may return an empty list, or undef, # and $@ is not guaranteed to be undefined. my ($success, $result_body, $result_header) = eval { (1, $deserializer->deserialize( $response )); }; if (defined $success) { return wantarray ? ($result_body, $result_header) : $result_body; } elsif (blessed $@) { #}&& $@->isa('SOAP::WSDL::SOAP::Typelib::Fault11')) { return $@; } #else return $deserializer->generate_fault({ code => 'soap:Server', role => 'urn:localhost', message => "Error deserializing message: $@. \n" . "Message was: \n$response" }); } # end sub deserialize_message sub interpret_messages { my ($svc_discover, $services, @responses ) = @_; my @results; foreach my $response ( @responses ) { if($verbose) { print "Received message:\n" . $response . "\n"; } my $result = deserialize_message($svc_discover, $response); if(not $result) { print "Error deserializing message. No message returned from deserializer.\n" if $verbose; next; } my $xaddr; foreach my $l_xaddr (split ' ', $result->get_ProbeMatch()->get_XAddrs()) { # find IPv4 address print "l_xaddr = $l_xaddr\n" if $verbose; if($l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]|) { $xaddr = $l_xaddr; last; } else { print STDERR "Unable to find IPv4 address from xaddr $l_xaddr\n"; } } # No usable address found next if not $xaddr; # ignore multiple responses from one service next if defined $services->{$xaddr}; $services->{$xaddr} = 1; print "$xaddr, " . $svc_discover->get_soap_version() . ", "; print "("; my $scopes = $result->get_ProbeMatch()->get_Scopes(); my $count = 0; my %scopes; foreach my $scope(split ' ', $scopes) { if($scope =~ m|onvif://www\.onvif\.org/(.+)/(.*)|) { my ($attr, $value) = ($1,$2); if( 0 < $count ++) { print ", "; } print $attr . "=\'" . $value . "\'"; $scopes{$attr} = $value; } } print ")\n"; push @results, { xaddr=>$xaddr, soap_version => $svc_discover->get_soap_version(), scopes => \%scopes, }; } return @results; } # end sub interpret_messages # functions sub discover { my ( $soap_version ) = @_; my @results; ## collect all responses my @responses = (); no warnings 'redefine'; *WSDiscovery::TransportUDP::_notify_response = sub { my ($transport, $response) = @_; push @responses, $response; }; ## try both soap versions my $uuid_gen = Data::UUID->new(); if ( ( ! $soap_version ) or ( $soap_version eq '1.1' ) ) { my %services; if($verbose) { print "Probing for SOAP 1.1\n" } my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ # no_dispatch => '1', }); $svc_discover->set_soap_version('1.1'); my $uuid = $uuid_gen->create_str(); my $result = $svc_discover->ProbeOp( { # WSDiscovery::Types::ProbeType Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType Scopes => { value => '' }, }, WSDiscovery10::Elements::Header->new({ Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, MessageID => { value => "urn:uuid:$uuid" }, To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, }) ); print $result . "\n" if $verbose; push @results, interpret_messages($svc_discover, \%services, @responses); @responses = (); } # end if doing soap 1.1 if ( ( ! $soap_version ) or ( $soap_version eq '1.2' ) ) { my %services; if($verbose) { print "Probing for SOAP 1.2\n" } my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ # no_dispatch => '1', }); $svc_discover->set_soap_version('1.2'); # copies of the same Probe message must have the same MessageID. # This is not a copy. So we generate a new uuid. my $uuid = $uuid_gen->create_str(); # Everyone else, like the nodejs onvif code and odm only ask for NetworkVideoTransmitter my $result = $svc_discover->ProbeOp( { # WSDiscovery::Types::ProbeType xmlattr => { 'xmlns:dn' => 'http://www.onvif.org/ver10/network/wsdl', }, Types => 'dn:NetworkVideoTransmitter', # QNameListType Scopes => { value => '' }, }, WSDiscovery10::Elements::Header->new({ Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, MessageID => { value => "urn:uuid:$uuid" }, To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, }) ); print $result . "\n" if $verbose; push @results, interpret_messages($svc_discover, \%services, @responses); } # end if doing soap 1.2 return @results; } sub profiles { my ( $client ) = @_; my $endpoint = $client->get_endpoint('media'); if ( ! $endpoint ) { print "No media enpoint for client.\n"; return; } my $result = $endpoint->GetProfiles( { } ,, ); if ( ! $result ) { print "No result from GetProfiles\n"; return; } if($verbose) { print "Received message:\n" . $result . "\n"; } my $profiles = $result->get_Profiles(); foreach my $profile ( @{ $profiles } ) { my $token = $profile->attr()->get_token() ; print $token . ", " . $profile->get_Name() . ", " . $profile->get_VideoEncoderConfiguration()->get_Encoding() . ", " . $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Width() . ", " . $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Height() . ", " . $profile->get_VideoEncoderConfiguration()->get_RateControl()->get_FrameRateLimit() . ", "; # Specification gives conflicting values for unicast stream types, try both. # http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast' ) { $result = $client->get_endpoint('media')->GetStreamUri( { StreamSetup => { # ONVIF::Media::Types::StreamSetup Stream => $streamtype, # StreamType Transport => { # ONVIF::Media::Types::Transport Protocol => 'RTSP', # TransportProtocol }, }, ProfileToken => $token, # ReferenceToken } ,, ); last if $result; } die $result if not $result; # print $result . "\n"; print $result->get_MediaUri()->get_Uri() . "\n"; } # end foreach profile # # use message parser without schema validation ??? # } sub move { my ($client, $dir) = @_; my $result = $client->get_endpoint('ptz')->GetNodes( { } ,, ); die $result if not $result; print $result . "\n"; } # end sub move sub metadata { my ( $client ) = @_; my $result = $client->get_endpoint('media')->GetMetadataConfigurations( { } ,, ); die $result if not $result; print $result . "\n"; $result = $client->get_endpoint('media')->GetVideoAnalyticsConfigurations( { } ,, ); die $result if not $result; print $result . "\n"; # $result = $client->get_endpoint('analytics')->GetServiceCapabilities( { } ,, ); # die $result if not $result; # print $result . "\n"; } 1; __END__ =head1 NAME ZoneMinder::ONVIF - perl module to access onvif functions for ZoneMinder =head1 SYNOPSIS use ZoneMinder::ONVIF; =head1 DESCRIPTION This is a module to contain useful functions and import all the other modules required for ONVIF to work. =head2 EXPORT None by default. =head1 SEE ALSO http://www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/0000755000000000000000000000000013365153155022357 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm0000644000000000000000000002417213365153155024037 0ustar rootroot# ========================================================================== # # ZoneMinder Axis version 2 API Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the Axis V2 API camera control # protocol # package ZoneMinder::Control::AxisV2; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Axis V2 Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); #print( "http://$address/$cmd\n" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub cameraReset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "/axis-cgi/admin/restart.cgi"; $self->sendCmd( $cmd ); } sub moveConUp { my $self = shift; Debug( "Move Up" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=up"; $self->sendCmd( $cmd ); } sub moveConDown { my $self = shift; Debug( "Move Down" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=down"; $self->sendCmd( $cmd ); } sub moveConLeft { my $self = shift; Debug( "Move Left" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=left"; $self->sendCmd( $cmd ); } sub moveConRight { my $self = shift; Debug( "Move Right" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=right"; $self->sendCmd( $cmd ); } sub moveConUpRight { my $self = shift; Debug( "Move Up/Right" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=upright"; $self->sendCmd( $cmd ); } sub moveConUpLeft { my $self = shift; Debug( "Move Up/Left" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=upleft"; $self->sendCmd( $cmd ); } sub moveConDownRight { my $self = shift; Debug( "Move Down/Right" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=downright"; $self->sendCmd( $cmd ); } sub moveConDownLeft { my $self = shift; Debug( "Move Down/Left" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=downleft"; $self->sendCmd( $cmd ); } sub moveMap { my $self = shift; my $params = shift; my $xcoord = $self->getParam( $params, 'xcoord' ); my $ycoord = $self->getParam( $params, 'ycoord' ); Debug( "Move Map to $xcoord,$ycoord" ); my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}."&imageheight=".$self->{Monitor}->{Height}; $self->sendCmd( $cmd ); } sub moveRelUp { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=$step"; $self->sendCmd( $cmd ); } sub moveRelDown { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=-$step"; $self->sendCmd( $cmd ); } sub moveRelLeft { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); Debug( "Step Left $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$step"; $self->sendCmd( $cmd ); } sub moveRelRight { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); Debug( "Step Right $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$step"; $self->sendCmd( $cmd ); } sub moveRelUpRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up/Right $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelUpLeft { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up/Left $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelDownRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down/Right $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelDownLeft { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down/Left $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep"; $self->sendCmd( $cmd ); } sub zoomRelTele { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Tele" ); my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step"; $self->sendCmd( $cmd ); } sub zoomRelWide { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Wide" ); my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step"; $self->sendCmd( $cmd ); } sub focusRelNear { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Near" ); my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step"; $self->sendCmd( $cmd ); } sub focusRelFar { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Far" ); my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step"; $self->sendCmd( $cmd ); } sub focusAuto { my $self = shift; Debug( "Focus Auto" ); my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=on"; $self->sendCmd( $cmd ); } sub focusMan { my $self = shift; Debug( "Focus Manual" ); my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=off"; $self->sendCmd( $cmd ); } sub irisRelOpen { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Open" ); my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step"; $self->sendCmd( $cmd ); } sub irisRelClose { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Close" ); my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step"; $self->sendCmd( $cmd ); } sub irisAuto { my $self = shift; Debug( "Iris Auto" ); my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=on"; $self->sendCmd( $cmd ); } sub irisMan { my $self = shift; Debug( "Iris Manual" ); my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=off"; $self->sendCmd( $cmd ); } sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Clear Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=home"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9821W_Y2k.pm0000644000000000000000000005330513365153155024421 0ustar rootroot# ========================================================================== # # ZoneMinder FOSCAM version 1.0 API Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================================= # # This module FI8620_Y2k.pm contains the implementation of API camera control # For FOSCAM FI8620 Dome PTZ Camera (This cam support only H264 streaming) # V1.0 Le 09 AOUT 2013 - production usable for the script but not for the camera "reboot itself" # If you wan't to contact me i understand French and English, precise ZoneMinder in subject # My name is Christophe DAPREMONT my email is christophe_y2k@yahoo.fr # # ========================================================================================= package ZoneMinder::Control::FI9821W_Y2k; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # =================================================================================================================================== # # FI9821 FOSCAM PT H264 Control Protocol # with Firmware version V1.2.1.1 (latest at 09/08/2013) # based with the latest buggy CGI doc from FOSCAM ( http://foscam.us/forum/cgi-sdk-for-hd-camera-t6045.html ) # This IPCAM work under ZoneMinder V1.25 from alternative source of code # from this svn at https://svn.unixmedia.net/public/zum/trunk/zum/ # Many Thanks to "MASTERTHEKNIFE" for the excellent speed optimisation ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=17652 ) # And to "NEXTIME" for the recent source update and incredible plugins ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=20587 ) # And all people helping ZoneMinder dev. # # -FUNCTION: display on OSD # speed is progressive in function of where you click on arrow ========> # speed low=/ \=speed high # =================================================================================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); # Set $osd to "off" if you wan't disabled OSD i need to place this variable in another script because # this script is reload at every command ,if i want the button on/off (Focus MAN) for OSD works... my $osd = "on"; sub new { my $class = shift; my $id = shift; my $self = ZoneMinder::Control->new( $id ); bless( $self, $class ); srand( time() ); return $self; } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return( $self->{$name} ); } Fatal( "Can't access $name member of object of class $class" ); } sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; my ($user, $password) = split /:/, $self->{Monitor}->{ControlDevice}; if ( ! $password ) { $password = $user; $user = 'admin'; } $user = 'admin' if ! $user; $password = 'pwd' if ! $password; $cmd .= "&usr=$user&pwd=$password"; printMsg( $cmd, "Tx" ); my $url; if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { $url = $self->{Monitor}->{ControlAddress}; } else { $url = "http://".$self->{Monitor}->{ControlAddress}; } $url .= "/cgi-bin/CGIProxy.fcgi?cmd=$cmd%26".time; printMsg( $url, "Tx" ); my $req = HTTP::Request->new( GET=>$url ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub reset { my $self = shift; Debug ( "Reset = setup camera FI9821W" ); # Setup OSD my $cmd = "setOSDSetting%26isEnableTimeStamp%3D0%26isEnableDevName%3D1%26dispPos%3D0%26isEnabledOSDMask%3D0"; $self->sendCmd( $cmd ); # Setup For Stream=0 Resolution=720p Bandwidth=4M FPS=30 KeyFrameInterval/GOP=100 VBR=ON $cmd = "setVideoStreamParam%26streamType%3D0%26resolution%3D0%26bitRate%3D4194304%26frameRate%3D30%26GOP%3D100%26isVBR%3D1"; $self->sendCmd( $cmd ); # Setup For Infrared AUTO $cmd = "setInfraLedConfig%26Mode%3D1"; $self->sendCmd( $cmd ); # Reset image settings $cmd = "resetImageSetting"; $self->sendCmd( $cmd ); } sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "ptzStopRun"; $self->sendCmd( $cmd ); $cmd = "setDevName%26devName%3D."; $self->sendCmd( $cmd ); $cmd = "setOSDSetting%26isEnableDevName%3D1"; $self->sendCmd( $cmd ); } sub autoStop { my $self = shift; my $autostop = shift; if( $autostop ) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "ptzStopRun"; $self->sendCmd( $cmd ); } } sub moveConUp { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Up" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Up $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveUp"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDown { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalization if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Down" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Down $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveDown"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $panspeed > 4 ) { $panspeed = 4; } if ( $panspeed < 0 ) { $panspeed = 0; } Debug( "Move Left" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$panspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveLeft"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $panspeed = abs($panspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $panspeed > 4 ) { $panspeed = 4; } if ( $panspeed < 0 ) { $panspeed = 0; } Debug( "Move Right" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$panspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveRight"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConUpLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Up Left" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Up Left $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveTopLeft"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConUpRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Up Right" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Up Right $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveTopRight"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDownLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Down Left" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Down Left $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveBottomLeft"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDownRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Down Right" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Down Right $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveBottomRight"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub zoomConTele { my $self = shift; Debug( "Zoom-Tele=MANU IR LED ON" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DManual IR LED Switch ON"; $self->sendCmd( $cmd ); } my $cmd = "setInfraLedConfig%26mode%3D1"; $self->sendCmd( $cmd ); $cmd = "openInfraLed"; $self->sendCmd( $cmd ); } sub zoomConWide { my $self = shift; Debug( "Zoom-Wide=MANU IR LED OFF" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DManual IR LED Switch OFF"; $self->sendCmd( $cmd ); } my $cmd = "setInfraLedConfig%26mode%3D1"; $self->sendCmd( $cmd ); $cmd = "closeInfraLed"; $self->sendCmd( $cmd ); } sub wake { my $self = shift; Debug( "Wake=AUTO IR LED" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DAuto IR LED Mode"; $self->sendCmd( $cmd ); } my $cmd = "setInfraLedConfig%26mode%3D0"; $self->sendCmd( $cmd ); } sub focusConNear { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Focus Near=Sharpness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSharpness $speed"; $self->sendCmd( $cmd ); $cmd = "setOSDSetting%26isEnableDevName%3D1"; $self->sendCmd( $cmd ); } my $cmd = "setSharpness%26sharpness%3D$speed"; $self->sendCmd( $cmd ); # La variable speed ne fonctionne pas en paramรจtre du focus, alors je l'utilise pour dรฉfinir la durรฉe de la commande # le rรฉsulat est identique $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub focusConFar { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Focus Far" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSharpness $speed"; $self->sendCmd( $cmd ); } my $cmd = "setSharpness%26sharpness%3D$speed"; $self->sendCmd( $cmd ); # La variable speed ne fonctionne pas en paramรจtre du focus alors je l'utilise pour dรฉfinir la durรฉe de la commande # le rรฉsulat est identique $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub focusAuto { my $self = shift; Debug( "Focus Auto=Reset Sharpness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DReset Sharpness"; $self->sendCmd( $cmd ); } my $cmd = "setSharpness%26sharpness%3D10"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub focusMan { my $self = shift; Debug( "Focus Manu=Reset Sharpness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DFOSCAM FI9821W Script V1.0 By Christophe_y2k"; $self->sendCmd( $cmd ); } } sub whiteConIn { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConIn=brightness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DBrightness $speed"; $self->sendCmd( $cmd ); } my $cmd = "setBrightness%26brightness%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteConOut { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConOut=Contrast" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DContrast $speed"; $self->sendCmd( $cmd ); } my $cmd = "setContrast%26constrast%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteAuto { my $self = shift; Debug( "White Auto=Brightness Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DBrightness Reset"; $self->sendCmd( $cmd ); } my $cmd = "setBrightness%26brightness%3D50"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteMan { my $self = shift; Debug( "White Manuel=Contrast Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DContrast Reset"; $self->sendCmd( $cmd ); } my $cmd = "setContrast%26constrast%3D44"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConOpen { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConOpen=Saturation" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSaturation $speed"; $self->sendCmd( $cmd ); } my $cmd = "setSaturation%26saturation%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConClose { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConClose=Hue" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DHue $speed"; $self->sendCmd( $cmd ); } my $cmd = "setHue%26hue%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisAuto { my $self = shift; Debug( "Iris Auto=Saturation Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSaturation Reset"; $self->sendCmd( $cmd ); } my $cmd = "setSaturation%26saturation%3D30"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisMan { my $self = shift; Debug( "Iris Manuel=Hue Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DHue Reset"; $self->sendCmd( $cmd ); } my $cmd = "setHue%26hue%3D6"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 16 ) ) { Debug( "Clear Preset $preset" ); my $cmd = "ptzDeletePresetPoint%26name%3D$preset"; $self->sendCmd( $cmd ); Debug( "Set Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSet Preset $preset"; $self->sendCmd( $cmd ); } $cmd = "ptzAddPresetPoint%26name%3D$preset"; $self->sendCmd( $cmd ); } } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 16 ) ) { Debug( "Goto Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DGoto Preset $preset"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D0"; $self->sendCmd( $cmd ); $cmd = "ptzGotoPresetPoint%26name%3D$preset"; $self->sendCmd( $cmd ); } } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Control::FI9821W - Perl extension for FOSCAM FI9821W =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm0000644000000000000000000001101013365153155025237 0ustar rootroot# ========================================================================== # # ZoneMinder Vivotek ePTZ Control Protocol Module # Copyright (C) 2015 Robin Daermann # # 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. # # ========================================================================== # # This module contains the implementation of the Vivotek ePTZ camera control # protocol # package ZoneMinder::Control::Vivotek_ePTZ; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Vivotek ePTZ Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); Debug( "Camera open" ); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; } sub printMsg { my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my ($self, $cmd, $speedcmd) = @_; my $result = undef; printMsg( $speedcmd, "Tx" ); printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET => "http://" . $self->{Monitor}->{ControlAddress} . "/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" ); } return( $result ); } sub moveConUp { my ($self, $params) = @_; my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); Debug( "Move Up" ); $self->sendCmd( 'move=up', $speed ); } sub moveConDown { my ($self, $params) = @_; my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); Debug( "Move Down" ); $self->sendCmd( 'move=down', $speed ); } sub moveConLeft { my ($self, $params) = @_; my $speed = 'speedpan=-' . $params->{panspeed}; Debug( "Move Left" ); $self->sendCmd( 'move=left', $speed ); } sub moveConRight { my ($self, $params) = @_; my $speed = 'speedpan=' . ($params->{panspeed} - 6); Debug( "Move Right" ); $self->sendCmd( 'move=right', $speed ); } sub moveStop { my $self = shift; Debug( "Move Stop" ); # not implemented } sub zoomConTele { my ($self, $params) = @_; my $speed = 'speedzoom=' . ($params->{speed} - 6); Debug( "Zoom In" ); $self->sendCmd( 'zoom=tele', $speed ); } sub zoomConWide { my ($self, $params) = @_; my $speed = 'speedzoom=' . ($params->{speed} - 6); Debug( "Zoom Out" ); $self->sendCmd( 'zoom=wide', $speed ); } sub reset { my $self = shift; Debug( "Camera Reset" ); $self->sendCmd( 'move=home' ); } 1; __END__ =head1 NAME ZoneMinder::Control::Vivotek_ePTZ - ZoneMinder Perl extension for Vivotek ePTZ camera control protocol =head1 SYNOPSIS use ZoneMinder::Control::Vivotek_ePTZ; =head1 DESCRIPTION This module implements the ePTZ protocol used in various Vivotek IP cameras, developed with a Vivotek IB8369 model. Currently, only simple pan, tilt and zoom function is implemented. Presets will follow later. =head2 EXPORT None. =head1 SEE ALSO I would say, see ZoneMinder::Control documentation. But it is a stub. =head1 AUTHOR Robin Daermann Er.daermann@ids-services.deE =head1 COPYRIGHT AND LICENSE Copyright (C) 2015 by Robin Daermann This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8608W_Y2k.pm0000644000000000000000000005452213365153155024425 0ustar rootroot# V1.0 ==================================================================================== # # ZoneMinder FOSCAM version 1.0 API Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # V1.0 ==================================================================================== # # This module FI8608W_Y2k.pm contains the implementation of API camera control # For FOSCAM FI8608W PT Camera (This cam support only H264 streaming) # V1.0 Le 13 AOUT 2013 # If you wan't to contact me i understand French and English, precise ZoneMinder in subject # but i prefer via the ZoneMinder Forum # My name is Christophe DAPREMONT my email is christophe_y2k@yahoo.fr # # V1.0 ==================================================================================== package ZoneMinder::Control::FI8608W_Y2k; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # =================================================================================================================================== # # FI8608W FOSCAM PT H264 Control Protocol # with Firmware version V3.2.1.1.1-20120815 (latest at 13/08/2013) # based with the latest doc from FOSCAM ( http://foscam.us/forum/cgi-api-sdk-for-mjpeg-h-264-camera-t2986.html ) # This IPCAM work under ZoneMinder V1.25 from alternative source of code # from this svn at https://svn.unixmedia.net/public/zum/trunk/zum/ # Many Thanks to "MASTERTHEKNIFE" for the excellent speed optimisation ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=17652 ) # And to "NEXTIME" for the recent source update and incredible plugins ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=20587 ) # And all people helping ZoneMinder dev. # # -FUNCTIONALITIES: # -Move camera in 8 direction with arrow, the speed of movement is function # of the position of your mouse on the arrow. # Extremity of arrow equal to fastest speed of movement # Close the base of arrow to lowest speed of movement # for diagonaly you can click before the beginning of the arrow for low speed # In round center equal to stop to move and switch of latest OSD # -You can clic directly on the image that equal to click on arrow (for the left there is a bug in zoneminder speed is inverted) # -Zoom Tele switch ON InfraRed LED and stay to manual IR MODE # -Zoom Wide switch OFF InfraRed LED and stay to manual IR MODE # -Button WAKE switch to AUTO ON/OFF IR LED # -Button RESET to setup image at initial value # -8 Preset PTZ are implemented and functionnal # -This Script use for login "admin" this hardcoded and your password must setup in "Control Device" section # -This script is compatible with the basic authentification method used by mostly new camera based with hi3510 chipset # -AutoStop function is active and you must set up value (in sec example 0.7) under AutoStop section # or you can set up to 0 for disable it (in this case you need to click to the circle center for stop) # -"White In" to control Brightness, "auto" for restore the original value of Brightness # -"White Out" to control Contrast, "man" for restore the original value of Contrast # -"Iris Open" to control Saturation , "auto" for restore the original value of Saturation # -"Iris Close" to control Hue , "man" for restore the original value of Hue # -I use the OSD function of this cam for printing the command with the value # =================================================================================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); # Set $osd to "off" if you wan't disabled OSD i need to place this variable in another script because # this script is reload at every command ,if i want the button on/off (Focus MAN) for OSD works... my $osd = "on"; sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); # I solve the authentification problem with recent Foscam # I use perl Basic Authentification method my $ua = LWP::UserAgent->new(); my $req = HTTP::Request->new( GET =>"http://".$self->{Monitor}->{ControlAddress}."/web/cgi-bin/hi3510/".$cmd ); $req->authorization_basic('admin', $self->{Monitor}->{ControlDevice} ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub reset { my $self = shift; Debug( "Reset=Setup FI8608W" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Color RESET"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-brightness=0"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setimageattr&-contrast=37"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setimageattr&-hue=255"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setimageattr&-saturation=94"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setinfra&-status=auto"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setimageattr&-scene=auto"; $self->sendCmd( $cmd ); } sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "ptzctrl.cgi?-step=0&-act=stop"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=0&-name=."; $self->sendCmd( $cmd ); } sub autoStop { my $self = shift; my $autostop = shift; if( $autostop ) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "ptzctrl.cgi?-step=0&-act=stop"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=0&-name=."; $self->sendCmd( $cmd ); } } sub moveConUp { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 100 ) { $tiltspeed = 128; } if ( $tiltspeed < 10 ) { $tiltspeed = 1; } Debug( "Move Up" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Up $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=up"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$tiltspeed) ); } sub moveConDown { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 100 ) { $tiltspeed = 128; } if ( $tiltspeed < 10 ) { $tiltspeed = 1; } Debug( "Move Down" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Down $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=down"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$tiltspeed) ); } sub moveConLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 100 ) { $panspeed = 128; } if ( $panspeed < 10 ) { $panspeed = 1; } # Algorithme pour inverser la table de valeur de la flรจche gauche, (for invert the value) 63 ==> 1 et 1 ==> 63 ... $panspeed = abs($panspeed - 128) + 1; Debug( "Move Left" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=left"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$panspeed) ); } sub moveConRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 100 ) { $panspeed = 128; } if ( $panspeed < 10 ) { $panspeed = 1; } Debug( "Move Right" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=right"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$panspeed) ); } sub moveConUpLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 100 ) { $tiltspeed = 128; } if ( $tiltspeed < 10 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect value in the database if ( $panspeed > 100 ) { $panspeed = 128; } if ( $panspeed < 10 ) { $panspeed = 1; } Debug( "Move Con Up Left" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Up $tiltspeed Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=up"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$tiltspeed) ); my $cmd = "ptzctrl.cgi?-step=0&-act=left"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$panspeed) ); } sub moveConUpRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 100 ) { $tiltspeed = 128; } if ( $tiltspeed < 10 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 100 ) { $panspeed = 128; } if ( $panspeed < 10 ) { $panspeed = 1; } Debug( "Move Con Up Right" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Up $tiltspeed Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=up"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$tiltspeed) ); my $cmd = "ptzctrl.cgi?-step=0&-act=right"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$panspeed) ); } sub moveConDownLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 100 ) { $tiltspeed = 128; } if ( $tiltspeed < 10 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 100 ) { $panspeed = 128; } if ( $panspeed < 10 ) { $panspeed = 1; } Debug( "Move Con Down Left" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Down $tiltspeed Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=down"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$tiltspeed) ); my $cmd = "ptzctrl.cgi?-step=0&-act=left"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$panspeed) ); } sub moveConDownRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 100 ) { $tiltspeed = 128; } if ( $tiltspeed < 10 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 100 ) { $panspeed = 128; } if ( $panspeed < 10 ) { $panspeed = 1; } Debug( "Move Con Down Right" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Down $tiltspeed Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=down"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$tiltspeed) ); my $cmd = "ptzctrl.cgi?-step=0&-act=right"; $self->sendCmd( $cmd ); $self->autoStop( int(10000*$panspeed) ); } sub zoomConTele { my $self = shift; Debug( "Zoom-Tele=MANU IR LED ON" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Manual IR LED ON"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setinfra&-status=open"; $self->sendCmd( $cmd ); } sub zoomConWide { my $self = shift; Debug( "Zoom-Wide=MANU IR LED OFF" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Manual IR LED OFF"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setinfra&-status=close"; $self->sendCmd( $cmd ); } sub wake { my $self = shift; Debug( "Wake=AUTO IR LED" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Auto IR LED Mode"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setinfra&-status=auto"; $self->sendCmd( $cmd ); } sub whiteConIn { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConIn=brightness" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Brightness $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-brightness=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteConOut { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConOut=Contrast" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Contrast $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-contrast=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteAuto { my $self = shift; Debug( "White Auto=Brightness Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Brightness Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-brightness=0"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteMan { my $self = shift; Debug( "White Manuel=Contrast Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Contrast Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-contrast=37"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConOpen { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect value in the database if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConOpen=Saturation" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Saturation $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-saturation=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConClose { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect value in the database if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConClose=Hue" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Hue $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-hue=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisAuto { my $self = shift; Debug( "Iris Auto=Saturation Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Saturation Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-saturation=94"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisMan { my $self = shift; Debug( "Iris Manuel=Hue Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Hue Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-hue=255"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 8 ) ) { Debug( "Set Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=PresetSet $preset"; $self->sendCmd( $cmd ); } my $preset = $preset - 1; my $cmd = "preset.cgi?-act=set&-status=1&-number=$preset"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 8 ) ) { Debug( "Goto Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=PresetGoto $preset"; $self->sendCmd( $cmd ); } my $preset = $preset - 1; my $cmd = "preset.cgi?-act=goto&-number=$preset"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Control::FI-8608W - Perl extension for FOSCAM FI-8608W by Christophe_Y2k =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/DericamP2.pm0000644000000000000000000003136713365153155024475 0ustar rootroot# ========================================================================== # # ZoneMinder Dericam P2 Control Protocol Module # Copyright (C) Roman Dissertori # # 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. # # ========================================================================== # # This module contains the implementation of the Dericam P2 device control protocol # package ZoneMinder::Control::DericamP2; use 5.006; use strict; use warnings; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our %CamParams = (); # ========================================================================== # # Dericam P2 Control Protocol # # On ControlAddress use the format : # USERNAME:PASSWORD@ADDRESS:PORT # eg : admin:@10.1.2.1:80 # zoneminder:zonepass@10.0.100.1:40000 # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); Info( "http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed:'".$res->status_line()."'" ); } return( $result ); } sub getCamParams { my $self = shift; my $cmd = "cgi-bin/hi3510/param.cgi?cmd=getimageattr"; my $req = $self->sendCmd( $cmd ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { # Parse results setting values in %FCParams my $content = $res->decoded_content; while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { $CamParams{$1} = $2; } } else { Error( "Error check failed:'".$res->status_line()."'" ); } } #autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; my $stop_command = shift; my $autostop = shift; if( $stop_command && $autostop) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "decoder_control.cgi?command=".$stop_command; $self->sendCmd( $cmd ); } } # Reset the Camera sub reset { my $self = shift; Debug( "Camera Reset" ); # Move to default position my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=home"; $self->sendCmd( $cmd ); # Reset all other values to default $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-image_type=1&-default=on"; $self->sendCmd( $cmd ); } # Reboot Camera (on Sleep button) sub sleep { my $self = shift; Debug( "Camera Reboot" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=sysreboot"; $self->sendCmd( $cmd ); } # Stop the Camera sub moveStop { my $self = shift; Debug( "Camera Stop" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=stop"; $self->sendCmd( $cmd ); } #Up Arrow sub moveConUp { my $self = shift; my $stop_command = "1"; Debug( "Move Up" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=up&-speed=45"; $self->sendCmd( $cmd ); #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Down Arrow sub moveConDown { my $self = shift; my $stop_command = "3"; Debug( "Move Down" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=down&-speed=45"; $self->sendCmd( $cmd ); #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Left Arrow sub moveConLeft { my $self = shift; my $stop_command = "5"; Debug( "Move Left" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=left&-speed=45"; $self->sendCmd( $cmd ); #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Right Arrow sub moveConRight { my $self = shift; my $stop_command = "7"; Debug( "Move Right" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=right&-speed=45"; $self->sendCmd( $cmd ); #$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Zoom In sub zoomConTele { my $self = shift; Debug( "Zoom Tele" ); my $cmd = "decoder_control.cgi?command=18"; $self->sendCmd( $cmd ); } #Zoom Out sub zoomConWide { my $self = shift; Debug( "Zoom Wide" ); my $cmd = "decoder_control.cgi?command=16"; $self->sendCmd( $cmd ); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upright&-speed=45"; $self->sendCmd( $cmd ); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downright&-speed=45"; $self->sendCmd( $cmd ); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upleft&-speed=45"; $self->sendCmd( $cmd ); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downnleft&-speed=45"; $self->sendCmd( $cmd ); } #Set Camera Preset #Presets must be translated into values internal to the camera #Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=set&-number=".(($preset*2) + 28); $self->sendCmd( $cmd ); } } #Recall Camera Preset #Presets must be translated into values internal to the camera #Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=goto&-number=".(($preset*2) + 29); $self->sendCmd( $cmd ); } if ( $preset == 9 ) { $self->horizontalPatrol(); } if ( $preset == 10 ) { $self->verticalPatrol(); } } #Horizontal Patrol sub horizontalPatrol { my $self = shift; Debug( "Horizontal Patrol" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=hscan"; $self->sendCmd( $cmd ); } #Vertical Patrol sub verticalPatrol { my $self = shift; Debug( "Vertical Patrol" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=vscan"; $self->sendCmd( $cmd ); } # Increase Brightness sub irisAbsOpen { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} += $step; $CamParams{'brightness'} = 100 if ($CamParams{'brightness'} > 100); Debug( "Increase Brightness" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Decrease Brightness sub irisAbsClose { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} -= $step; $CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0); Debug( "Decrease Brightness" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Increase Contrast sub whiteAbsIn { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} += $step; $CamParams{'contrast'} = 100 if ($CamParams{'contrast'} > 100); Debug( "Increase Contrast" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } # Decrease Contrast sub whiteAbsOut { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} -= $step; $CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0); Debug( "Decrease Contrast" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } #TODO Saturation cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=44 [0-255] sub satIncrease { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'saturation'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'saturation'} += $step; $CamParams{'saturation'} = 255 if ($CamParams{'saturation'} > 255); Debug( "Increase Saturation" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'}; $self->sendCmd( $cmd ); } sub satDecrease { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'saturation'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'saturation'} -= $step; $CamParams{'saturation'} = 0 if ($CamParams{'saturation'} < 0); Debug( "Decrease Saturation" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'}; $self->sendCmd( $cmd ); } #TODO Sharpness cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=37 [0-100] sub sharpIncrease { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'sharpness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'sharpness'} += $step; $CamParams{'sharpness'} = 100 if ($CamParams{'sharpness'} > 100); Debug( "Increase Sharpness" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'}; $self->sendCmd( $cmd ); } sub sharpDecrease { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'sharpness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'sharpness'} -= $step; $CamParams{'sharpness'} = 0 if ($CamParams{'sharpness'} < 0); Debug( "Decrease Sharpness" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'}; $self->sendCmd( $cmd ); } #TODO Hue cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=37 [0-100] sub hueIncrease { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'hue'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'hue'} += $step; $CamParams{'hue'} = 100 if ($CamParams{'hue'} > 100); Debug( "Increase Hue" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'}; $self->sendCmd( $cmd ); } sub hueDecrease { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'hue'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'hue'} -= $step; $CamParams{'hue'} = 0 if ($CamParams{'hue'} < 0); Debug( "Decrease Hue" ); my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'}; $self->sendCmd( $cmd ); } 1; ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCC7210W.pm0000644000000000000000000002012513365153155024074 0ustar rootroot# ========================================================================== # # ZoneMinder IPCC-7210W IP Control Protocol Module, $Date: 2015-11-18$, $Revision: 0001$ # Modified for use with IPCC-7210W on Nov 2015 # # 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. # # ========================================================================== # # This module contains the implementation of the # IPCC-7210W IP camera control protocol # package ZoneMinder::Control::IPCC-7210W; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # IPCC-7210W IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); #print( "http://$address/$cmd\n" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} ); Info( "http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub cameraReset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } #Up Arrow sub moveConUp { my $self = shift; my $params = shift; Debug( "Move Up" ); my $cmd = "decoder_control.cgi?command=2&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Down Arrow sub moveConDown { my $self = shift; my $params = shift; Debug( "Move Down" ); my $cmd = "decoder_control.cgi?command=0&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Left Arrow sub moveConLeft { my $self = shift; my $params = shift; Debug( "Move Left" ); my $cmd = "decoder_control.cgi?command=4&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Right Arrow sub moveConRight { my $self = shift; my $params = shift; Debug( "Move Right" ); my $cmd = "decoder_control.cgi?command=6&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Up Right Arrow sub moveConUpRight { my $self = shift; my $params = shift; Debug( "Move Diagonally Up Right" ); my $cmd = "decoder_control.cgi?command=93&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Down Right Arrow sub moveConDownRight { my $self = shift; my $params = shift; Debug( "Move Diagonally Down Right" ); my $cmd = "decoder_control.cgi?command=91&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Up Left Arrow sub moveConUpLeft { my $self = shift; my $params = shift; Debug( "Move Diagonally Up Left" ); my $cmd = "decoder_control.cgi?command=92&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Down Left Arrow sub moveConDownLeft { my $self = shift; my $params = shift; Debug( "Move Diagonally Down Left" ); my $cmd = "decoder_control.cgi?command=90&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "decoder_control.cgi?command=1&onestep=1&"; $self->sendCmd( $cmd ); } #Move Camera to Home Position sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "decoder_control.cgi?command=4&onestep=0&"; $self->sendCmd( $cmd ); } # zoom out sub zoomRelTele { my $self = shift; Debug( "Zoom Tele" ); my $cmd = "camera_control.cgi?param=17&value=1&"; $self->sendCmd( $cmd ); } #zoom in sub zoomRelWide { my $self = shift; Debug( "Zoom Wide" ); my $cmd = "camera_control.cgi?param=18&value=1&"; $self->sendCmd( $cmd ); } #Set preset sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 30 + (($preset-1)*2); Debug( "Set Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd&onestep=0&sit=$presetCmd&"; $self->sendCmd( $cmd ); } #Goto preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 31 + (($preset-1)*2); Debug( "Goto Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd&onestep=0&sit=$presetCmd&"; $self->sendCmd( $cmd ); } #Turn IR on sub wake { my $self = shift; Debug( "Wake - IR on" ); my $cmd = "camera_control.cgi?param=14&value=1&"; $self->sendCmd( $cmd ); } #Turn IR off sub sleep { my $self = shift; Debug( "Sleep - IR off" ); my $cmd = "camera_control.cgi?param=14&value=0&"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::IPCC-7210W - Perl extension for IPCC-7210W PTZ control =head1 SYNOPSIS use ZoneMinder::Control::IPCC =head1 DESCRIPTION This script provides Pan/Tilt/Zoom control for IPCC-7210W camera. =head1 SEE ALSO ZoneMinder::Control::Wanscam =head1 AUTHOR =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/MaginonIPC.pm0000644000000000000000000001623013365153155024643 0ustar rootroot# ========================================================================== # # ZoneMinder Maginon Supra IPC Camera Control Protocol Module, # Copyright (C) 2017 Martin Gutenbrunner (martin.gutenbrunner@SPAMsonic.net) # # 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. # # ========================================================================== # # This module contains the implementation of the Maginon Supra IPC camera # procotol version. # package ZoneMinder::Control::MaginonIPC; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Maginon Supra IPC IP Camera Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; #my $result = undef; printMsg( $cmd, "Tx" ); my $url = "http://".$self->{Monitor}->{ControlAddress}."$cmd"; # Info($url); my $req = HTTP::Request->new( GET=>$url ); my $res = $self->{ua}->request($req); return( !undef ); } sub moveStop { Debug("moveStop"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=1"; $self->sendCmd( $cmd ); } sub moveConUp { Debug("moveConUp"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=0"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConDown { Debug("moveConDown"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=2"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConLeft { Debug("moveConLeft"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=4"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConRight { Debug("moveConRight"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=6"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConUpRight { Debug("moveConUpRight"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=91"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConUpLeft { Debug("moveConUpLeft"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=90"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConDownRight { Debug("moveConDownRight"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=93"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConDownLeft { Debug("moveConDownLeft"); my $self = shift; my $params = shift; my $cmd = "/decoder_control.cgi?command=92"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Info( "Set Preset $preset" ); my $cmdNum; if ($preset == 1) { $cmdNum = 30; } elsif ($preset == 2) { $cmdNum = 32; } elsif ($preset == 3) { $cmdNum = 34; } elsif ($preset == 4) { $cmdNum = 36; } else { $cmdNum = 36; } my $cmd = "/decoder_control.cgi?command=$cmdNum"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Info( "Goto Preset $preset" ); my $cmdNum; if ($preset == 1) { $cmdNum = 31; } elsif ($preset == 2) { $cmdNum = 33; } elsif ($preset == 3) { $cmdNum = 35; } elsif ($preset == 4) { $cmdNum = 37; } else { $cmdNum = 37; } my $cmd = "/decoder_control.cgi?command=$cmdNum"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Control::MaginonIPC - Zoneminder PTZ control module for the Maginon Supra-IPC 40 IP Camera =head1 SYNOPSIS use ZoneMinder::Control::MaginonIPC; blah blah blah =head1 DESCRIPTION This is for Zoneminder PTZ control module for the Maginon Supra-IPC 40 camera. It probably also works with other models. =head2 EXPORT None by default. =head1 SEE ALSO www.zoneminder.com =head1 AUTHOR Martin Gutenbrunner, Emartin.gutenbrunner@gmx.atE =head1 COPYRIGHT AND LICENSE Copyright (C) 2017 by Martin Gutenbrunner This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm0000644000000000000000000002626513365153155024513 0ustar rootrootpackage ZoneMinder::Control::Trendnet; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # You do not need to change the REALM, but you can get slightly faster response # by setting so that the first auth request succeeds. # # The username and password should be passed in the ControlAddress field but you # can set them here if you want. # our $REALM = ''; our $PROTOCOL = 'http://'; our $USERNAME = 'admin'; our $PASSWORD = ''; our $ADDRESS = ''; use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); sub open { my $self = shift; $self->loadMonitor(); if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?https?:\/\/)?(?[^:@]+)?:?(?[^\/@]+)?@?(?
.*)$/ ) ) { $PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL}; $USERNAME = $+{USERNAME} if $+{USERNAME}; $PASSWORD = $+{PASSWORD} if $+{PASSWORD}; $ADDRESS = $+{ADDRESS} if $+{ADDRESS}; } else { Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress}); $ADDRESS = $self->{Monitor}->{ControlAddress}; } if ( !($ADDRESS =~ /:/) ) { Error('You generally need to also specify the port. I will append :80'); $ADDRESS .= ':80'; } use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent('ZoneMinder Control Agent/'.$ZoneMinder::Base::ZM_VERSION); $self->{state} = 'closed'; # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) Debug ( "sendCmd credentials control address:'".$ADDRESS ."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD ."'" ); $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); # Detect REALM my $res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi'); if ( $res->is_success ) { $self->{state} = 'open'; return; } if ( $res->status_line() eq '401 Unauthorized' ) { my $headers = $res->headers(); foreach my $k ( keys %$headers ) { Debug("Initial Header $k => $$headers{$k}"); } if ( $$headers{'www-authenticate'} ) { my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; if ( $tokens =~ /\w+="([^"]+)"/i ) { if ( $REALM ne $1 ) { $REALM = $1; Debug("Changing REALM to $REALM"); $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); $res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi'); if ( $res->is_success() ) { $self->{state} = 'open'; return; } Error('Authentication still failed after updating REALM' . $res->status_line); $headers = $res->headers(); foreach my $k ( keys %$headers ) { Debug("Initial Header $k => $$headers{$k}"); } # end foreach } else { Error('Authentication failed, not a REALM problem'); } } else { Error('Failed to match realm in tokens'); } # end if } else { Debug('No headers line'); } # end if headers } # end if $res->status_line() eq '401 Unauthorized' } # end sub open sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug($msg.'['.$msg_len.']'); } sub sendCmd { # This routine is used for all moving, which are all GET commands... my $self = shift; my $cmd = shift; my $url = $PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi?command='.$cmd; my $res = $self->{ua}->get($url); Debug('sendCmd command: ' . $url); if ( $res->is_success ) { return !undef; } Error("Error check failed: '".$res->status_line()."' cmd:'".$cmd."'"); return; } sub sendCmdPost { # # This routine is used for setting/clearing presets and IR commands, which are POST commands... # my $self = shift; my $url = shift; my $form = shift; my $result = undef; if ( $url eq undef ) { Error('url passed to sendCmdPost is undefined.'); return -1; } #Debug('sendCmdPost url: ' . $url . ' cmd: ' . $cmd); my $res; $res = $self->{ua}->post( $PROTOCOL.$ADDRESS.$url, Referer=>$PROTOCOL.$ADDRESS.$url, Content=>$form ); Debug("sendCmdPost credentials control to: $PROTOCOL$ADDRESS$url realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'"); if ( $res->is_success ) { return !undef; } Error("sendCmdPost Error check failed: '".$res->status_line()."' cmd:"); return $result; } # end sub sendCmdPost sub move { my $self = shift; my $panSteps = shift; my $tiltSteps = shift; my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps"; $self->sendCmd($cmd); } sub moveRelUpLeft { my $self = shift; Debug('Move Up Left'); $self->move(-3, 3); } sub moveRelUp { my $self = shift; Debug('Move Up'); $self->move(0, 3); } sub moveRelUpRight { my $self = shift; Debug('Move Up Right'); $self->move(3, 3); } sub moveRelLeft { my $self = shift; Debug('Move Left'); $self->move(-3, 0); } sub moveRelRight { my $self = shift; Debug('Move Right'); $self->move(3, 0); } sub moveRelDownLeft { my $self = shift; Debug('Move Down Left'); $self->move(-3, -3); } sub moveRelDown { my $self = shift; Debug('Move Down'); $self->move(0, -3); } sub moveRelDownRight { my $self = shift; Debug('Move Down Right'); $self->move(3, -3); } # moves the camera to center on the point that the user clicked on in the video image. # This isn't mega accurate but good enough for most purposes sub moveMap { # If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!) # They scale the movement and are here to compensate for manufacturing variation. # It's never going to be perfect, so just get somewhere in the ballpark and call it a day. # (Don't forget to kill the zmcontrol process while tweaking!) # 1280x800 my $hscale = 31; my $vscale = 25; # 1280x800 with fisheye #my $hscale = 15; #my $vscale = 15; # 640x400 #my $hscale = 14; #my $vscale = 12; my $self = shift; my $params = shift; my $xcoord = $self->getParam( $params, 'xcoord' ); my $ycoord = $self->getParam( $params, 'ycoord' ); my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale; my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale; $hor = int($hor); $ver = -1 * int($ver); Debug("Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver"); $self->move($hor, $ver); } # **** PRESETS **** # # OK, presets work a little funky but they DO work, provided you define them # in order and don't skip any. # # The problem is that when you load the web page for this camera, it gives a # list of preset names tied to index numbers. # So let's say you have four presets... A, B, C, and D, and defined them in # that order. # So A is index 0, B is index 1, C is index 2, D is index 3. When you tell # the camera to go to a preset, you actually tell it by number, not by name. # (So "Go to D" is really "go to index 3".) # # Now let's say somebody deletes C via the camera's web GUI. The camera # re-numbers the existing presets A=0, B=1, D=2. # There's really no easy way for ZM to discover this re-numbering, so # zoneminder would still send "go to preset 3" thinking # it's telling the camera to go to point D. In actuality it's telling the # camera to go to a preset that no longer exists. # # As long as you define your presets in order (i.e. define preset 1, then # preset 2, then preset 3, etc.) everything will work just # fine in ZoneMinder. # # (Home preset needs to be set via the camera's web gui, and is unaffected by # any of this.) # # So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only # through!) ZM AND DON'T SKIP ANY. # sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam($params, 'preset'); my $cmd = "presetName=$preset&command=del"; my $url = '/eng/admin/cam_control.cgi'; Debug('presetClear: ' . $preset . ' cmd: ' . $cmd); $self->sendCmdPost($url,{presetName=>$preset, command=>'del'}); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam($params, 'preset'); my $cmd = "presetName=$preset&command=add"; my $url = '/eng/admin/cam_control.cgi'; Debug('presetSet ' . $preset . ' cmd: ' . $cmd); $self->sendCmdPost($url,{presetName=>$preset, command=>'add', Submit=>'Add'}); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam($params, 'preset'); $preset = $preset - 1; Debug("Goto Preset $preset"); my $cmd = "goto_preset_position&index=$preset"; $self->sendCmd($cmd); } sub presetHome { my $self = shift; Debug('Home Preset'); my $cmd = 'go_home'; $self->sendCmd($cmd); } # # **** IR CONTROLS **** # # # Wake: Force IR on, always. (always night mode) # # Sleep: Force IR off, always. (always day mode) # # Reset: Automatic IR mode. (day/night mode determined by camera) # sub wake { # force IR on ("always night mode") my $self = shift; my $url = '/eng/admin/adv_audiovideo.cgi'; my $cmd = 'irMode=3'; Debug('Wake -- IR on'); $self->sendCmdPost($url,$cmd); } sub sleep { # force IR off ("always day mode") my $self = shift; my $url = '/eng/admin/adv_audiovideo.cgi'; my $cmd = 'irMode=2'; Debug('Sleep -- IR off'); $self->sendCmdPost($url,$cmd); } sub reset { # IR auto my $self=shift; my $url = '/eng/admin/adv_audiovideo.cgi'; my $cmd = 'irMode=0'; Debug('Reset -- IR auto'); $self->sendCmdPost($url,$cmd); } 1; __END__ =head1 NAME ZoneMinder::Control::Trendnet - Perl module for Trendnet cameras =head1 SYNOPSIS use ZoneMinder::Control::Trendnet; place this in /usr/share/perl5/ZoneMinder/Control =head1 DESCRIPTION This module contains the implementation of the Trendnet # IP camera control protocol. Has been tested with TV-IP862IC Under control capability: * Main: Can wake, can sleep, can reset * Move: Can move, can move diagonally, can move mapped, can move relative * Pan: Can pan * Tilt: Can tilt * Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.) Under control tab in the monitor itself: Controllable Control type is the name you gave it in control capability above Control address is the camera's ip address AND web port. example: 192.168.1.1:80 You can also put the authentication information here and change the protocol to https using something like https://admin:password@192.168.1.1:80 =head2 EXPORT None by default. =head1 COPYRIGHT AND LICENSE Copyright (C) 2018 ZoneMinder LLC This library 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 library 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. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/mjpgStreamer.pm0000644000000000000000000001112313365153155025353 0ustar rootroot# ========================================================================== # # ZoneMinder mjpg STreamer Control Protocol Module, $Date: 2007-11-04 17:30:29 +0000 (Sun, 04 Nov 2007) $, $Revision: 2229 $ # Copyright (C) 2003, 2004, 2005, 2006 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the mjpg streamer camera control # protocol # package ZoneMinder::Control::mjpgStreamer; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # mjpgSTreamer Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); Debug( "Camera open" ); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub Up { my $self = shift; $self->moveConUp(); } sub Down { my $self = shift; $self->moveConDown(); } sub Left { my $self = shift; $self->moveConLeft(); } sub Right { my $self = shift; $self->moveConRight(); } sub reset { my $self = shift; $self->cameraReset(); } sub cameraReset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "?action=command&command=reset_pan_tilt"; $self->sendCmd( $cmd ); } sub moveConUp { my $self = shift; Debug( "Move Up" ); my $cmd = "?action=command&command=tilt_minus"; $self->sendCmd( $cmd ); } sub moveConDown { my $self = shift; Debug( "Move Down" ); my $cmd = "?action=command&command=tilt_plus"; $self->sendCmd( $cmd ); } sub moveConLeft { my $self = shift; Debug( "Move Left" ); my $cmd = "?action=command&command=pan_plus"; $self->sendCmd( $cmd ); } sub moveConRight { my $self = shift; Debug( "Move Right" ); my $cmd = "?action=command&command=pan_minus"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2005 by Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8620_Y2k.pm0000644000000000000000000006323513365153155024271 0ustar rootroot# V1.1 ==================================================================================== # # ZoneMinder FOSCAM version 1.0 API Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # V1.1 ==================================================================================== # # This module FI8620_Y2k.pm contains the implementation of API camera control # For FOSCAM FI8620 Dome PTZ Camera (This cam support only H264 streaming) # V0.1b Le 01 JUIN 2013 # V0.2b Le 11 JUILLET 2013 # V0.5b Le 24 JUILLET 2013 # V0.6b Le 01 AOUT 2013 - # V1.0 Le 04 AOUT 2013 - production usable if you do not use preset ptz # V1.1 Le 11 AOUT 2013 - put a cosmetic update source code # If you wan't to contact me i understand French and English, precise ZoneMinder in subject # My name is Christophe DAPREMONT my email is christophe_y2k@yahoo.fr # # V1.1 ==================================================================================== package ZoneMinder::Control::FI8620_Y2k; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # =================================================================================================================================== # # FI8620 FOSCAM Dome PTZ H264 Control Protocol # with Firmware version V3.2.2.2.1-20120815 (latest at 04/08/2013) # based with the latest buggy CGI doc from FOSCAM ( http://foscam.us/forum/cgi-api-sdk-for-mjpeg-h-264-camera-t2986.html ) # This IPCAM work under ZoneMinder V1.25 from alternative source of code # from this svn at https://svn.unixmedia.net/public/zum/trunk/zum/ # Many Thanks to "MASTERTHEKNIFE" for the excellent speed optimisation ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=17652 ) # And to "NEXTIME" for the recent source update and incredible plugins ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=20587 ) # And all people helping ZoneMinder dev. # # -FUNCTIONALITIES: # -Move camera in 8 direction with arrow, the speed of movement is function # of the position of your mouse on the arrow. # Extremity of arrow equal to fastest speed of movement # Close the base of arrow to lowest speed of movement # for diagonaly you can click before the beginning of the arrow for low speed # In round center equal to stop to move # -You can clic directly on the image that equal to click on arrow (for the left there is a bug in zoneminder speed is inverted) # -Zoom Tele/Wide with time control to simulate speed because speed value do not work (buggy firmware or not implemented on this cam) # -Focus Near/Far with time control to simulate speed because speed value do not work (buggy firmware or not implemented on this cam) # -Autofocus is automatic when you move again or can be setting by autofocus button # -8 Preset PTZ are implemented but the firmware is buggy and that do not work # You Need to configure ZoneMinder PANSPEED & TILTSEPPED & ZOOMSPEED 1 to 63 by 1 step # -This Script use for login "admin" this hardcoded and your password must setup in "Control Device" section # -This script is compatible with the basic authentification method used by mostly new camera # -AutoStop function is active and you must set up value (in sec example 0.5) under AutoStop section # or you can set up to 0 for disable it but the camera never stop to move and trust me, she can move all the night... # (you need to click to the center arrow for stop) # -"White In" to control Brightness, "auto" for restore the original value of Brightness # -"White Out" to control Contrast, "man" for restore the original value of Contrast # -"Iris Open" to control Saturation , "auto" for restore the original value of Saturation # -"Iris Close" to control Hue , "man" for restore the original value of Hue # -Another cool stuff i use the OSD function of this cam for printing the command with the value # The button of Focus "Man" is for enable or disable OSD but that do not work ( it's my bug... i'm very very new with perl ) # =================================================================================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); # Set $osd to "off" if you wan't disabled OSD i need to place this variable in another script because # this script is reload at every command ,if i want the button on/off (Focus MAN) for OSD works... my $osd = "on"; sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); # I solve the authentification problem with recent Foscam # I use perl Basic Authentification method my $ua = LWP::UserAgent->new(); my $req = HTTP::Request->new( GET =>"http://".$self->{Monitor}->{ControlAddress}."/web/cgi-bin/hi3510/".$cmd ); $req->authorization_basic('admin', $self->{Monitor}->{ControlDevice} ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "ptzctrl.cgi?-step=0&-act=stop"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=0&-name=."; $self->sendCmd( $cmd ); } sub autoStop { my $self = shift; my $autostop = shift; if( $autostop ) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "ptzctrl.cgi?-step=0&-act=stop"; $self->sendCmd( $cmd ); my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=0&-name=."; $self->sendCmd( $cmd ); } } sub moveConUp { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 59 ) { $tiltspeed = 63; } if ( $tiltspeed < 6 ) { $tiltspeed = 1; } Debug( "Move Up" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Up $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=up&-speed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDown { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 59 ) { $tiltspeed = 63; } if ( $tiltspeed < 6 ) { $tiltspeed = 1; } Debug( "Move Down" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Down $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=down&-speed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 59 ) { $panspeed = 63; } if ( $panspeed < 6 ) { $panspeed = 1; } # Algorithme pour inverser la table de valeur de la flรจche gauche, (for invert the value) 63 ==> 1 et 1 ==> 63 ... $panspeed = abs($panspeed - 63) + 1; Debug( "Move Left" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=left&-speed=$panspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 59 ) { $panspeed = 63; } if ( $panspeed < 6 ) { $panspeed = 1; } Debug( "Move Right" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Move Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=right&-speed=$panspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConUpLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 59 ) { $tiltspeed = 63; } if ( $tiltspeed < 6 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect value in the database if ( $panspeed > 59 ) { $panspeed = 63; } if ( $panspeed < 6 ) { $panspeed = 1; } Debug( "Move Con Up Left" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Up $tiltspeed Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=up&-speed=$tiltspeed"; $self->sendCmd( $cmd ); my $cmd = "ptzctrl.cgi?-step=0&-act=left&-speed=$panspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConUpRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 59 ) { $tiltspeed = 63; } if ( $tiltspeed < 6 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 59 ) { $panspeed = 63; } if ( $panspeed < 6 ) { $panspeed = 1; } Debug( "Move Con Up Right" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Up $tiltspeed Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=up&-speed=$tiltspeed"; $self->sendCmd( $cmd ); my $cmd = "ptzctrl.cgi?-step=0&-act=right&-speed=$panspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDownLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 59 ) { $tiltspeed = 63; } if ( $tiltspeed < 6 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 59 ) { $panspeed = 63; } if ( $panspeed < 6 ) { $panspeed = 1; } Debug( "Move Con Down Left" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Down $tiltspeed Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=down&-speed=$tiltspeed"; $self->sendCmd( $cmd ); my $cmd = "ptzctrl.cgi?-step=0&-act=left&-speed=$panspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDownRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $tiltspeed > 59 ) { $tiltspeed = 63; } if ( $tiltspeed < 6 ) { $tiltspeed = 1; } my $panspeed = $self->getParam( $params, 'panspeed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $panspeed > 59 ) { $panspeed = 63; } if ( $panspeed < 6 ) { $panspeed = 1; } Debug( "Move Con Down Right" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Down $tiltspeed Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=down&-speed=$tiltspeed"; $self->sendCmd( $cmd ); my $cmd = "ptzctrl.cgi?-step=0&-act=right&-speed=$panspeed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub zoomConTele { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 59 ) { $speed = 63; } if ( $speed < 6 ) { $speed = 1; } Debug( "Zoom-Tele" ); # I use OSD Function to send the speed used for determining the time before stop the order if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Zoom Tele $speed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=zoomin"; $self->sendCmd( $cmd ); # The variable speed does not work with zoom setting, so I used to set the duration of the order # the result is identical $self->autoStop( int(10000*$speed) ); } sub zoomConWide { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 59 ) { $speed = 63; } if ( $speed < 6 ) { $speed = 1; } Debug( "Zoom-Wide" ); # I use the feature OSD (OnScreenDisplay). Variable speed as a basis for calculating the duration of the zoom order if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Zoom Wide $speed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=zoomout"; $self->sendCmd( $cmd ); # The variable speed does not work with zoom setting, so I used to set the duration of the order # the result is identical $self->autoStop( int(10000*$speed) ); } sub focusConNear { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 59 ) { $speed = 63; } if ( $speed < 6 ) { $speed = 1; } Debug( "Focus Near" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Focus Near $speed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=focusout&-speed=$speed"; $self->sendCmd( $cmd ); # The variable speed does not work with focus setting, so I used to set the duration of the order # the result is identical $self->autoStop( int(10000*$speed) ); } sub focusConFar { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 59 ) { $speed = 63; } if ( $speed < 6 ) { $speed = 1; } Debug( "Focus Far" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Focus Far $speed"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-step=0&-act=focusin&-speed=$speed"; $self->sendCmd( $cmd ); # The variable speed does not work with focus setting, so I used to set the duration of the order # the result is identical $self->autoStop( int(10000*$speed) ); } sub focusAuto { my $self = shift; Debug( "Focus Auto" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Focus Auto"; $self->sendCmd( $cmd ); } my $cmd = "ptzctrl.cgi?-act=auto&-step=1"; $self->sendCmd( $cmd ); } sub focusMan { my $self = shift; Debug( "Focus Manu=OSD ON OFF" ); if ( $osd eq "on" ) { $osd = "off"; my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=OSD $osd"; $self->sendCmd( $cmd ); $self->autoStop( int(1000000*0.5) ); } if ( $osd eq "off" ) { $osd = "on"; my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=OSD $osd"; $self->sendCmd( $cmd ); $self->autoStop( int(1000000*0.5) ); } } sub whiteConIn { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConIn=brightness" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Brightness $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-brightness=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteConOut { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect possible value in the database, and for realise at low and high speed an more precise moving if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConOut=Contrast" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Contrast $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-contrast=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteAuto { my $self = shift; Debug( "White Auto=Brightness Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Brightness Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-brightness=120"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteMan { my $self = shift; Debug( "White Manuel=Contrast Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Contrast Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-contrast=140"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConOpen { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect value in the database if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConOpen=Saturation" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Saturation $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-saturation=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConClose { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Standardization for incorrect value in the database if ( $speed > 255 ) { $speed = 255; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConClose=Hue" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Hue $speed"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-hue=$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisAuto { my $self = shift; Debug( "Iris Auto=Saturation Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Saturation Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-saturation=150"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisMan { my $self = shift; Debug( "Iris Manuel=Hue Reset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=Hue Reset"; $self->sendCmd( $cmd ); } my $cmd = "param.cgi?cmd=setimageattr&-hue=255"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 8 ) ) { Debug( "Clear Preset $preset" ); my $cmd = "preset.cgi?-act=set&-status=0&-number=$preset"; $self->sendCmd( $cmd ); Debug( "Set Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=PresetSet $preset"; $self->sendCmd( $cmd ); } my $cmd = "preset.cgi?-act=set&-status=1&-number=$preset"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 8 ) ) { Debug( "Goto Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "param.cgi?cmd=setoverlayattr&-region=1&-show=1&-name=PresetGoto $preset"; $self->sendCmd( $cmd ); } my $cmd = "preset.cgi?-act=goto&-number=$preset"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Control::FI8620 - Perl extension for FOSCAM FI8620 =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/LoftekSentinel.pm0000644000000000000000000002310513365153155025644 0ustar rootroot# ========================================================================== # # ZoneMinder Loftek Sentinel IP Control Protocol Module, $Date: 2009-11-25 09:20:00 +0000 (Wed, 04 Nov 2009) $, $Revision: 0001 $ # Copyright (C) 2001-2008 Philip Coombes # Original modification for use with Foscam FI8908W IP Camera by Dave Harris # Updated by Ivan Francolin Martinez # Converted for use with Loftek Sentinal IP Camera by Andrew Bauer (knnniggett@users.sourceforge.net) # # 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. # # ========================================================================== # # This module contains the implementation of the Loftek Sentinel IP camera control # protocol # package ZoneMinder::Control::LoftekSentinel; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our %CamParams = (); # ========================================================================== # # Loftek Sentinel IP Control Protocol # # On ControlAddress use the format : # USERNAME:PASSWORD@ADDRESS:PORT # eg : admin:@10.1.2.1:80 # zoneminder:zonepass@10.0.100.1:40000 # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed:'".$res->status_line()."'" ); } return( $result ); } sub getCamParams { my $self = shift; my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/get_camera_params.cgi" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { # Parse results setting values in %FCParams my $content = $res->decoded_content; while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { $CamParams{$1} = $2; } } else { Error( "Error check failed:'".$res->status_line()."'" ); } } #autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; my $stop_command = shift; my $autostop = shift; if( $stop_command && $autostop) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "decoder_control.cgi?command=".$stop_command; $self->sendCmd( $cmd ); } } # Reset the Camera sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } #Up Arrow sub moveConUp { my $self = shift; my $stop_command = "1"; Debug( "Move Up" ); my $cmd = "decoder_control.cgi?command=0"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Down Arrow sub moveConDown { my $self = shift; my $stop_command = "3"; Debug( "Move Down" ); my $cmd = "decoder_control.cgi?command=2"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Left Arrow sub moveConLeft { my $self = shift; my $stop_command = "5"; Debug( "Move Left" ); my $cmd = "decoder_control.cgi?command=4"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Right Arrow sub moveConRight { my $self = shift; my $stop_command = "7"; Debug( "Move Right" ); my $cmd = "decoder_control.cgi?command=6"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Zoom In sub zoomConTele { my $self = shift; my $stop_command = "17"; Debug( "Zoom Tele" ); my $cmd = "decoder_control.cgi?command=18"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Zoom Out sub zoomConWide { my $self = shift; my $stop_command = "19"; Debug( "Zoom Wide" ); my $cmd = "decoder_control.cgi?command=16"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); $self->moveConUp( ); $self->moveConRight( ); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); $self->moveConDown( ); $self->moveConRight( ); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); $self->moveConUp( ); $self->moveConLeft( ); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); $self->moveConDown( ); $self->moveConLeft( ); } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "decoder_control.cgi?command=1"; $self->sendCmd( $cmd ); } #Set Camera Preset #Presets must be translated into values internal to the camera #Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "decoder_control.cgi?command=".(($preset*2) + 28); $self->sendCmd( $cmd ); } } #Recall Camera Preset #Presets must be translated into values internal to the camera #Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "decoder_control.cgi?command=".(($preset*2) + 29); $self->sendCmd( $cmd ); } if ( $preset == 9 ) { $self->horizontalPatrol(); } if ( $preset == 10 ) { $self->horizontalPatrolStop(); } } #Horizontal Patrol - Vertical Patrols are not supported sub horizontalPatrol { my $self = shift; Debug( "Horizontal Patrol" ); my $cmd = "decoder_control.cgi?command=20"; $self->sendCmd( $cmd ); } #Horizontal Patrol Stop sub horizontalPatrolStop { my $self = shift; Debug( "Horizontal Patrol Stop" ); my $cmd = "decoder_control.cgi?command=21"; $self->sendCmd( $cmd ); } # Increase Brightness sub irisAbsOpen { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} += $step; $CamParams{'brightness'} = 255 if ($CamParams{'brightness'} > 255); Debug( "Iris $CamParams{'brightness'}" ); my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Decrease Brightness sub irisAbsClose { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} -= $step; $CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0); Debug( "Iris $CamParams{'brightness'}" ); my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Increase Contrast sub whiteAbsIn { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} += $step; $CamParams{'contrast'} = 6 if ($CamParams{'contrast'} > 6); Debug( "Iris $CamParams{'contrast'}" ); my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } # Decrease Contrast sub whiteAbsOut { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} -= $step; $CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0); Debug( "Iris $CamParams{'contrast'}" ); my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } 1; ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8918W.pm0000644000000000000000000001711713365153155023703 0ustar rootroot# Modified on Jun 19 2016 by PP # Changes made # - modified command to work properly and pick up credentials from Control Device # - the old script did not stop moving- added autostop # (note that mjpeg cameras have onestep but that is too granular) # - You need to set "user=xxx&pwd=yyy" in the ControlDevice field (NOT usr like in Foscam HD) # ========================================================================== # # ZoneMinder Foscam FI8918W IP Control Protocol Module, $Date: 2009-11-25 09:20:00 +0000 (Wed, 04 Nov 2009) $, $Revision: 0001 $ # Copyright (C) 2001-2008 Philip Coombes # Modified for use with Foscam FI8918W IP Camera by Dave Harris # Modified Feb 2011 by Howard Durdle (http://durdl.es/x) to: # fix horizontal panning, add presets and IR on/off # use Control Device field to pass username and password # Modified May 2014 by Arun Horne (http://arunhorne.co.uk) to: # use HTTP basic auth as required by firmware 11.37.x.x upward # # 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. # # ========================================================================== # # This module contains the implementation of the Foscam FI8918W IP camera control # protocol # package MyAgent; use base 'LWP::UserAgent'; package ZoneMinder::Control::FI8918W; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Foscam FI8918W IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); our $stop_command; sub open { my $self = shift; $self->loadMonitor(); $self->{ua} = MyAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/" ); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); # PP Old cameras also support onstep=1 but it is too granular. Instead using moveCon and stop after interval # PP - cleaned up URL to take it properly from Control device # Control device needs to be of format user=xxx&pwd=yyy my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd"."&".$self->{Monitor}->{ControlDevice}); print ("Sending $req\n"); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error REALLY check failed:'".$res->status_line()."'" ); Error ("Cmd:".$req); } return( $result ); } sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } # PP - in all move operations, added auto stop after timeout #Up Arrow sub moveConUp { my $self = shift; Debug( "Move Up" ); my $cmd = "decoder_control.cgi?command=0"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Down Arrow sub moveConDown { my $self = shift; Debug( "Move Down" ); my $cmd = "decoder_control.cgi?command=2"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Left Arrow sub moveConLeft { my $self = shift; Debug( "Move Left" ); my $cmd = "decoder_control.cgi?command=6"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Right Arrow sub moveConRight { my $self = shift; Debug( "Move Right" ); my $cmd = "decoder_control.cgi?command=4"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Right Arrow sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); my $cmd = "decoder_control.cgi?command=90"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Down Right Arrow sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); my $cmd = "decoder_control.cgi?command=92"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Left Arrow sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); my $cmd = "decoder_control.cgi?command=91"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Down Left Arrow sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); my $cmd = "decoder_control.cgi?command=93"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "decoder_control.cgi?command=1"; $self->sendCmd( $cmd ); } # PP - imported from 9831 - autostop after usleep sub autoStop { my $self = shift; my $autostop = shift; if( $autostop ) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "decoder_control.cgi?command=1"; $self->sendCmd( $cmd ); } } #Move Camera to Home Position sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "decoder_control.cgi?command=25"; $self->sendCmd( $cmd ); } #Set preset sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 30 + ($preset*2); Debug( "Set Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd"; $self->sendCmd( $cmd ); } #Goto preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 31 + ($preset*2); Debug( "Goto Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd"; $self->sendCmd( $cmd ); } #Turn IR on sub wake { my $self = shift; Debug( "Wake - IR on" ); my $cmd = "decoder_control.cgi?command=95"; $self->sendCmd( $cmd ); } #Turn IR off sub sleep { my $self = shift; Debug( "Sleep - IR off" ); my $cmd = "decoder_control.cgi?command=94"; $self->sendCmd( $cmd ); } 1; __END__ =head1 FI8918W ZoneMinder::Database - Perl extension for FOSCAM FI8918W =head1 SYNOPSIS Control script for Foscam MJPEG 8918W cameras. =head1 DESCRIPTION You need to set "user=xxx&pwd=yyy" in the ControlDevice field of the control tab for that monitor. Auto TimeOut should be 1. Don't set it to less - processes start crashing :) NOTE: unlike HD foscam cameras, this one uses "user" not "usr" in the control device =head2 EXPORT None by default. =head1 SEE ALSO =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm0000644000000000000000000002015713365153155024513 0ustar rootroot# Modified on 2017-11-17 by Ognyan Bankov # ========================================================================== # # ZoneMinder Floureon 1080p IP Control Protocol Module, $Date: 2017-11-17 09:20:00 +0000 $, $Revision: 0001 $ # Copyright (C) 2017 Ognyan Bankov # # 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. # # ========================================================================== # # This module contains the implementation of the Floureon 1080p 18x (Model: BT-HD54F) IP camera control # protocol. It should work with other Floureon cameras too. # # package MyAgent; use base 'LWP::UserAgent'; package ZoneMinder::Control::Floureon; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Floureon IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); our $stop_command; sub open { my $self = shift; $self->loadMonitor(); $self->{ua} = MyAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/" ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd"."&".$self->{Monitor}->{ControlDevice}); print ("Sending $req\n"); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error REALLY check failed:'".$res->status_line()."'" ); Error ("Cmd:".$req); } return( $result ); } sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } # PP - in all move operations, added auto stop after timeout #Up Arrow sub moveConUp { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); Debug( "Move Up" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=1&PanSpeed=6&TiltSpeed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop($tiltspeed); } #Down Arrow sub moveConDown { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); Debug( "Move Down" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=2&PanSpeed=6&TiltSpeed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop($tiltspeed); } #Left Arrow sub moveConLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); Debug( "Move Left" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=3&PanSpeed=$panspeed&TiltSpeed=6"; $self->sendCmd( $cmd ); $self->autoStop($panspeed); } #Right Arrow sub moveConRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); Debug( "Move Right" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=4&PanSpeed=$panspeed&TiltSpeed=6"; $self->sendCmd( $cmd ); $self->autoStop($panspeed); } #Diagonally Up Right Arrow sub moveConUpRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); Debug( "Move Diagonally Up Right" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=7&PanSpeed=$panspeed&TiltSpeed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop($tiltspeed); } #Diagonally Down Right Arrow sub moveConDownRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); Debug( "Move Diagonally Down Right" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=8&PanSpeed=$panspeed&TiltSpeed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop($tiltspeed); } #Diagonally Up Left Arrow sub moveConUpLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); Debug( "Move Diagonally Up Left" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=5&PanSpeed=$panspeed&TiltSpeed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop($tiltspeed); } #Diagonally Down Left Arrow sub moveConDownLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); Debug( "Move Diagonally Down Left" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Direction=6&PanSpeed=$panspeed&TiltSpeed=$tiltspeed"; $self->sendCmd( $cmd ); $self->autoStop($tiltspeed); } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Stop=0"; $self->sendCmd( $cmd ); } sub zoomConTele { my $self = shift; my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Zoom=1"; $self->sendCmd( $cmd ); $self->autoStop(); } sub zoomConWide { my $self = shift; my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Zoom=0"; $self->sendCmd( $cmd ); $self->autoStop(); } sub focusConNear { my $self = shift; my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Focus=1"; $self->sendCmd( $cmd ); $self->autoStop(); } sub focusConFar { my $self = shift; my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Focus=0"; $self->sendCmd( $cmd ); $self->autoStop(); } sub irisConOpen { my $self = shift; my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Iris=1"; $self->sendCmd( $cmd ); $self->autoStop(); } sub irisConClose { my $self = shift; my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Iris=0"; $self->sendCmd( $cmd ); $self->autoStop(); } #Set preset sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=0"; $self->sendCmd( $cmd ); } #Goto preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=1"; $self->sendCmd( $cmd ); } sub autoStop { my $self = shift; my $timeout = shift; if ($timeout) { if ($timeout > 1) { usleep(100000*$timeout); } } Debug( "Auto Stop" ); my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&Stop=0"; $self->sendCmd( $cmd ); } 1; __END__ =head1 Floureon ZoneMinder::Database - Perl extension for Floureon 1080P =head1 SYNOPSIS Control script for Floureon 1080P IP camera =head1 DESCRIPTION When setuping you monitor in the "Control" tab: 1. Select "Control type": Floureon 1080P 2. Leave "Control device" empty 3. Fill "Control Address" like username:password@ip/domain. Example: admin:admin123@192.168.1.110 =head2 EXPORT None by default. =head1 SEE ALSO =head1 AUTHOR Ognyan Bankov, Eogibankov@gmail.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2017 Ognyan Bankov This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/PSIA.pm0000644000000000000000000002330013365153155023447 0ustar rootrootpackage ZoneMinder::Control::PSIA; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $REALM = 'TV-IP450PI'; our $USERNAME = 'admin'; our $PASSWORD = ''; our $ADDRESS = ''; our $PROTOCOL = 'http://'; use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use ZoneMinder::Database qw(zmDbConnect); sub open { my $self = shift; $self->loadMonitor(); if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?https?:\/\/)?(?[^:@]+)?:?(?[^\/@]+)?@?(?
.*)$/ ) ) { $PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL}; $USERNAME = $+{USERNAME} if $+{USERNAME}; $PASSWORD = $+{PASSWORD} if $+{PASSWORD}; $ADDRESS = $+{ADDRESS} if $+{ADDRESS}; } else { Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress}); $ADDRESS = $self->{Monitor}->{ControlAddress}; } if ( !($ADDRESS =~ /:/) ) { Error('You generally need to also specify the port. I will append :80'); $ADDRESS .= ':80'; } use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'closed'; Debug( "sendCmd credentials control address:'".$ADDRESS ."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD ."'" ); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); # Detect REALM my $req = HTTP::Request->new(GET=>$PROTOCOL . $ADDRESS . "/PSIA/PTZ/channels"); my $res = $self->{ua}->request($req); if ($res->is_success) { $self->{state} = 'open'; return; } elsif (! $res->is_success) { Debug("Need newer REALM"); if ( $res->status_line() eq '401 Unauthorized' ) { my $headers = $res->headers(); foreach my $k ( keys %$headers ) { Debug("Initial Header $k => $$headers{$k}"); } # end foreach if ( $$headers{'www-authenticate'} ) { my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; if ($tokens =~ /\w+="([^"]+)"/i) { $REALM = $1; Debug("Changing REALM to $REALM"); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); } # end if } else { Debug("No WWW-Authenticate header"); } # end if www-authenticate header } # end if $res->status_line() eq '401 Unauthorized' } # end elsif ! $res->is_success } sub close { my $self = shift; $self->{state} = 'closed'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendGetRequest { my $self = shift; my $url_path = shift; my $result = undef; my $url = $PROTOCOL . $ADDRESS . $url_path; my $req = HTTP::Request->new(GET=>$url); my $res = $self->{ua}->request($req); if ($res->is_success) { $result = !undef; } else { if ( $res->status_line() eq '401 Unauthorized' ) { Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); Error("Content was " . $res->content() ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error("Content was " . $res->content() ); } } if ( ! $result ) { Error("Error check failed: '".$res->status_line()); } } return($result); } sub sendPutRequest { my $self = shift; my $url_path = shift; my $content = shift; my $result = undef; my $url = $PROTOCOL . $ADDRESS . $url_path; my $req = HTTP::Request->new(PUT=>$url); if(defined($content)) { $req->content_type("application/x-www-form-urlencoded; charset=UTF-8"); $req->content('' . "\n" . $content); } my $res = $self->{ua}->request($req); if ($res->is_success) { $result = !undef; } else { if ( $res->status_line() eq '401 Unauthorized' ) { Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); Error("Content was " . $res->content() ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error("Content was " . $res->content() ); } } if ( ! $result ) { Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); } } return($result); } sub sendDeleteRequest { my $self = shift; my $url_path = shift; my $result = undef; my $url = $PROTOCOL . $ADDRESS . $url_path; my $req = HTTP::Request->new(DELETE=>$url); my $res = $self->{ua}->request($req); if ($res->is_success) { $result = !undef; } else { if ( $res->status_line() eq '401 Unauthorized' ) { Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); Error("Content was " . $res->content() ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error("Content was " . $res->content() ); } } if ( ! $result ) { Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); } } return($result); } sub move { my $self = shift; my $panPercentage = shift; my $tiltPercentage = shift; my $zoomPercentage = shift; my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps"; my $ptzdata = ''; $ptzdata .= '' . $panPercentage . ''; $ptzdata .= '' . $tiltPercentage . ''; $ptzdata .= '' . $zoomPercentage . ''; $ptzdata .= '500'; $ptzdata .= ''; $self->sendPutRequest("/PSIA/PTZ/channels/1/momentary", $ptzdata); } sub moveRelUpLeft { my $self = shift; Debug( "Move Up Left" ); $self->move(-50, 50, 0); } sub moveRelUp { my $self = shift; Debug( "Move Up" ); $self->move(0, 50, 0); } sub moveRelUpRight { my $self = shift; Debug( "Move Up Right" ); $self->move(50, 50, 0); } sub moveRelLeft { my $self = shift; Debug( "Move Left" ); $self->move(-50, 0, 0); } sub moveRelRight { my $self = shift; Debug( "Move Right" ); $self->move(50, 0, 0); } sub moveRelDownLeft { my $self = shift; Debug( "Move Down Left" ); $self->move(-50, -50, 0); } sub moveRelDown { my $self = shift; Debug( "Move Down" ); $self->move(0, -50, 0); } sub moveRelDownRight { my $self = shift; Debug( "Move Down Right" ); $self->move(50, -50, 0); } sub zoomRelTele { my $self = shift; Debug("Zoom Relative Tele"); $self->move(0, 0, 50); } sub zoomRelWide { my $self = shift; Debug("Zoom Relative Wide"); $self->move(0, 0, -50); } sub presetClear { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id; $self->sendDeleteRequest($url_path); } sub presetSet { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); my $dbh = zmDbConnect(1); my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?'; my $sth = $dbh->prepare($sql) or Fatal("Can't prepare sql '$sql': " . $dbh->errstr()); my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id) or Fatal("Can't execute sql '$sql': " . $sth->errstr()); my $control_preset_row = $sth->fetchrow_hashref(); my $new_label_name = $control_preset_row->{'Label'}; my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id; my $ptz_preset_data = ''; $ptz_preset_data .= '' . $preset_id . ''; $ptz_preset_data .= '' . $new_label_name . ''; $ptz_preset_data .= ''; $self->sendPutRequest($url_path, $ptz_preset_data); } sub presetGoto { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); my $url_path = '/PSIA/PTZ/channels/1/presets/' . $preset_id . '/goto'; $self->sendPutRequest($url_path); } 1; __END__ =head1 NAME ZoneMinder::Control::PSIA - Perl module for cameras implementing the PSIA (Physical Security Interoperability Alliance), IP Media Devices API specification =head1 SYNOPSIS use ZoneMinder::Control::PSIA; place this in /usr/share/perl5/ZoneMinder/Control =head1 DESCRIPTION This has so far been tested with: - Trendnet TV-IP450PI =head2 EXPORT None by default. =head1 COPYRIGHT AND LICENSE Copyright (C) 2018 ZoneMinder LLC This library 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 library 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. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoP.pm0000644000000000000000000004435713365153155024114 0ustar rootroot# ========================================================================== # # ZoneMinder Pelco-P Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the Pelco-P camera control # protocol # package ZoneMinder::Control::PelcoP; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Pelco-P Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use Time::HiRes qw( usleep ); use constant STX => 0xa0; use constant ETX => 0xaf; use constant COMMAND_GAP => 100000; # In ms sub open { my $self = shift; $self->loadMonitor(); use Device::SerialPort; $self->{port} = new Device::SerialPort( $self->{Monitor}->{ControlDevice} ); $self->{port}->baudrate(4800); $self->{port}->databits(8); $self->{port}->parity('none'); $self->{port}->stopbits(1); $self->{port}->handshake('none'); $self->{port}->read_const_time(50); $self->{port}->read_char_time(10); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; $self->{port}->close(); } sub printMsg { if ( logDebugging() ) { my $self = shift; my $msg = shift; my $prefix = shift || ""; $prefix = $prefix.": " if ( $prefix ); my $line_length = 16; my $msg_len = int(@$msg); my $msg_str = $prefix; for ( my $i = 0; $i < $msg_len; $i++ ) { if ( ($i > 0) && ($i%$line_length == 0) && ($i != ($msg_len-1)) ) { $msg_str .= sprintf( "\n%*s", length($prefix), "" ); } $msg_str .= sprintf( "%02x ", $msg->[$i] ); } $msg_str .= "[".$msg_len."]"; Debug( $msg_str ); } } sub sendCmd { my $self = shift; my $cmd = shift; my $ack = shift || 0; my $result = undef; # Pelco P protocol checksum is created by XOR'ing the first seven bytes in the message # including the first byte, the STX sync packet my $checksum = 0x00; for ( my $i = 0; $i < int(@$cmd); $i++ ) { $checksum ^= $cmd->[$i]; } $checksum &= 0xff; push( @$cmd, $checksum ); $self->printMsg( $cmd, "Tx" ); my $id = $cmd->[0] & 0xf; my $tx_msg = pack( "C*", @$cmd ); #print( "Tx: ".length( $tx_msg )." bytes\n" ); my $n_bytes = $self->{port}->write( $tx_msg ); if ( !$n_bytes ) { Error( "Write failed: $!" ); } if ( $n_bytes != length($tx_msg) ) { Error( "Incomplete write, only ".$n_bytes." of ".length($tx_msg)." written: $!" ); } if ( $ack ) { Debug( "Waiting for ack" ); my $max_wait = 3; my $now = time(); while( 1 ) { my ( $count, $rx_msg ) = $self->{port}->read(4); if ( $count ) { #print( "Rx1: ".$count." bytes\n" ); my @resp = unpack( "C*", $rx_msg ); printMsg( \@resp, "Rx" ); if ( $resp[0] = 0x80 + ($id<<4) ) { if ( ($resp[1] & 0xf0) == 0x40 ) { my $socket = $resp[1] & 0x0f; Debug( "Got ack for socket $socket" ); $result = !undef; } else { Error( "Got bogus response" ); } last; } else { Error( "Got message for camera ".(($resp[0]-0x80)>>4) ); } } if ( (time() - $now) > $max_wait ) { Warning( "Response timeout" ); last; } } } } sub remoteReset { my $self = shift; Debug( "Remote Reset" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x0f, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub resetDefaults { my $self = shift; Debug( "Reset Defaults" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x29, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub cameraOff { my $self = shift; Debug( "Camera Off" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x08, 0x00, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub cameraOn { my $self = shift; Debug( "Camera On" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x88, 0x00, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub autoScan { my $self = shift; Debug( "Auto Scan" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x90, 0x00, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub manScan { my $self = shift; Debug( "Manual Scan" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x10, 0x00, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub stop { my $self = shift; Debug( "Stop" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x00, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub moveConUp { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'tiltspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x08, 0x00, $speed, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveConDown { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'tiltspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x10, 0x00, $speed, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConLeft { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'panspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Left" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x04, $speed, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConRight { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'panspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Right" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x02, $speed, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConUpLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up/Left" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x0c, $panspeed, $tiltspeed, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConUpRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up/Right" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x0a, $panspeed, $tiltspeed, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConDownLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down/Left" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x14, $panspeed, $tiltspeed, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConDownRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down/Right" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x12, $panspeed, $tiltspeed, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveStop { my $self = shift; Debug( "Move Stop" ); $self->stop(); } sub flip180 { my $self = shift; Debug( "Flip 180" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, 0x21, ETX ); $self->sendCmd( \@msg ); } sub zeroPan { my $self = shift; Debug( "Zero Pan" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, 0x22, ETX ); $self->sendCmd( \@msg ); } sub _setZoomSpeed { my $self = shift; my $speed = shift; Debug( "Set Zoom Speed $speed" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x25, 0x00, $speed, ETX ); $self->sendCmd( \@msg ); } sub zoomStop { my $self = shift; Debug( "Zoom Stop" ); $self->stop(); $self->_setZoomSpeed( 0 ); } sub zoomConTele { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x01 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Zoom Tele" ); $self->_setZoomSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x20, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->zoomStop(); } } sub zoomConWide { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x01 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Zoom Wide" ); $self->_setZoomSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x40, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->zoomStop(); } } sub _setFocusSpeed { my $self = shift; my $speed = shift; Debug( "Set Focus Speed $speed" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x27, 0x00, $speed, ETX ); $self->sendCmd( \@msg ); } sub focusConNear { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x03 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Focus Near" ); $self->_setFocusSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x01, 0x00, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setFocusSpeed( 0 ); } } sub focusConFar { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x03 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Focus Far" ); $self->_setFocusSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x80, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setFocusSpeed( 0 ); } } sub focusStop { my $self = shift; Debug( "Focus Stop" ); $self->stop(); $self->_setFocusSpeed( 0 ); } sub focusAuto { my $self = shift; Debug( "Focus Auto" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x2b, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub focusMan { my $self = shift; Debug( "Focus Man" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x2b, 0x00, 0x02, ETX ); $self->sendCmd( \@msg ); } sub _setIrisSpeed { my $self = shift; my $speed = shift; Debug( "Set Iris Speed $speed" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x27, 0x00, $speed, ETX ); $self->sendCmd( \@msg ); } sub irisConClose { my $self = shift; my $params = shift; my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Iris Close" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x04, 0x00, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setIrisSpeed( 0 ); } } sub irisConOpen { my $self = shift; my $params = shift; my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Iris Open" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x02, 0x80, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setIrisSpeed( 0 ); } } sub irisStop { my $self = shift; Debug( "Iris Stop" ); $self->stop(); $self->_setIrisSpeed( 0 ); } sub irisAuto { my $self = shift; Debug( "Iris Auto" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x2d, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub irisMan { my $self = shift; Debug( "Iris Man" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x2d, 0x00, 0x02, ETX ); $self->sendCmd( \@msg ); } sub writeScreen { my $self = shift; my $params = shift; my $string = $self->getParam( $params, 'string' ); Debug( "Writing '$string' to screen" ); my @chars = unpack( "C*", $string ); for ( my $i = 0; $i < length($string); $i++ ) { my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x15, $i, $chars[$i], ETX ); $self->sendCmd( \@msg ); usleep( COMMAND_GAP ); } } sub clearScreen { my $self = shift; Debug( "Clear Screen" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x17, 0x00, 0x00, ETX ); $self->sendCmd( \@msg ); } sub clearPreset { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Clear Preset $preset" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x05, 0x00, $preset, ETX ); $self->sendCmd( \@msg ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Set Preset $preset" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x03, 0x00, $preset, ETX ); $self->sendCmd( \@msg ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Goto Preset $preset" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, $preset, ETX ); $self->sendCmd( \@msg ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my @msg = ( STX, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, 0x22, ETX ); $self->sendCmd( \@msg ); } sub reset { my $self = shift; Debug( "Reset" ); $self->remoteReset(); $self->resetDefaults(); } sub wake { my $self = shift; Debug( "Wake" ); $self->cameraOn(); } sub sleep { my $self = shift; Debug( "Sleep" ); $self->cameraOff(); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm0000644000000000000000000002231113365153155024035 0ustar rootroot# ========================================================================== # # ZoneMinder ONVIF Control Protocol Module # Copyright (C) Jan M. Hochstein # # 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. # # ========================================================================== # # This module contains the implementation of the ONVIF device control protocol # package ZoneMinder::Control::onvif; use 5.006; use strict; use warnings; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our %CamParams = (); # ========================================================================== # # ONVIF Control Protocol # # On ControlAddress use the format : # USERNAME:PASSWORD@ADDRESS:PORT # eg : admin:@10.1.2.1:80 # zoneminder:zonepass@10.0.100.1:40000 # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed:'".$res->status_line()."'" ); } return( $result ); } sub getCamParams { my $self = shift; my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/get_camera_params.cgi" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { # Parse results setting values in %FCParams my $content = $res->decoded_content; while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { $CamParams{$1} = $2; } } else { Error( "Error check failed:'".$res->status_line()."'" ); } } #autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; my $stop_command = shift; my $autostop = shift; if( $stop_command && $autostop) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "decoder_control.cgi?command=".$stop_command; $self->sendCmd( $cmd ); } } # Reset the Camera sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } #Up Arrow sub moveConUp { my $self = shift; my $stop_command = "1"; Debug( "Move Up" ); my $cmd = "decoder_control.cgi?command=0"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Down Arrow sub moveConDown { my $self = shift; my $stop_command = "3"; Debug( "Move Down" ); my $cmd = "decoder_control.cgi?command=2"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Left Arrow sub moveConLeft { my $self = shift; my $stop_command = "5"; Debug( "Move Left" ); my $cmd = "decoder_control.cgi?command=4"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Right Arrow sub moveConRight { my $self = shift; my $stop_command = "7"; Debug( "Move Right" ); my $cmd = "decoder_control.cgi?command=6"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Zoom In sub zoomConTele { my $self = shift; my $stop_command = "17"; Debug( "Zoom Tele" ); my $cmd = "decoder_control.cgi?command=18"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Zoom Out sub zoomConWide { my $self = shift; my $stop_command = "19"; Debug( "Zoom Wide" ); my $cmd = "decoder_control.cgi?command=16"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); $self->moveConUp( ); $self->moveConRight( ); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); $self->moveConDown( ); $self->moveConRight( ); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); $self->moveConUp( ); $self->moveConLeft( ); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); $self->moveConDown( ); $self->moveConLeft( ); } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "decoder_control.cgi?command=1"; $self->sendCmd( $cmd ); } #Set Camera Preset #Presets must be translated into values internal to the camera #Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "decoder_control.cgi?command=".(($preset*2) + 28); $self->sendCmd( $cmd ); } } #Recall Camera Preset #Presets must be translated into values internal to the camera #Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "decoder_control.cgi?command=".(($preset*2) + 29); $self->sendCmd( $cmd ); } if ( $preset == 9 ) { $self->horizontalPatrol(); } if ( $preset == 10 ) { $self->horizontalPatrolStop(); } } #Horizontal Patrol - Vertical Patrols are not supported sub horizontalPatrol { my $self = shift; Debug( "Horizontal Patrol" ); my $cmd = "decoder_control.cgi?command=20"; $self->sendCmd( $cmd ); } #Horizontal Patrol Stop sub horizontalPatrolStop { my $self = shift; Debug( "Horizontal Patrol Stop" ); my $cmd = "decoder_control.cgi?command=21"; $self->sendCmd( $cmd ); } # Increase Brightness sub irisAbsOpen { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} += $step; $CamParams{'brightness'} = 255 if ($CamParams{'brightness'} > 255); Debug( "Iris $CamParams{'brightness'}" ); my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Decrease Brightness sub irisAbsClose { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} -= $step; $CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0); Debug( "Iris $CamParams{'brightness'}" ); my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Increase Contrast sub whiteAbsIn { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} += $step; $CamParams{'contrast'} = 6 if ($CamParams{'contrast'} > 6); Debug( "Iris $CamParams{'contrast'}" ); my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } # Decrease Contrast sub whiteAbsOut { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} -= $step; $CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0); Debug( "Iris $CamParams{'contrast'}" ); my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } 1; ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Visca.pm0000644000000000000000000004610513365153155023770 0ustar rootroot# ========================================================================== # # ZoneMinder Visca Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the Visca camera control # protocol # package ZoneMinder::Control::Visca; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Visca Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use Time::HiRes qw( usleep ); use constant SYNC => 0xff; use constant COMMAND_GAP => 100000; # In ms sub open { my $self = shift; $self->loadMonitor(); use Device::SerialPort; $self->{port} = new Device::SerialPort( $self->{Monitor}->{ControlDevice} ); $self->{port}->baudrate(9600); $self->{port}->databits(8); $self->{port}->parity('none'); $self->{port}->stopbits(1); $self->{port}->handshake('rts'); $self->{port}->stty_echo(0); #$self->{port}->read_const_time(250); $self->{port}->read_char_time(2); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; $self->{port}->close(); } sub printMsg { if ( logDebugging() ) { my $self = shift; my $msg = shift; my $prefix = shift || ""; $prefix = $prefix.": " if ( $prefix ); my $line_length = 16; my $msg_len = int(@$msg); my $msg_str = $prefix; for ( my $i = 0; $i < $msg_len; $i++ ) { if ( ($i > 0) && ($i%$line_length == 0) && ($i != ($msg_len-1)) ) { $msg_str .= sprintf( "\n%*s", length($prefix), "" ); } $msg_str .= sprintf( "%02x ", $msg->[$i] ); } $msg_str .= "[".$msg_len."]"; Debug( $msg_str ); } } sub sendCmd { my $self = shift; my $cmd = shift; my $ack = shift || 0; my $cmp = shift || 0; my $result = undef; $self->printMsg( $cmd, "Tx" ); my $id = $cmd->[0] & 0xf; my $tx_msg = pack( "C*", @$cmd ); #print( "Tx: ".length( $tx_msg )." bytes\n" ); my $n_bytes = $self->{port}->write( $tx_msg ); if ( !$n_bytes ) { Error( "Write failed: $!" ); } if ( $n_bytes != length($tx_msg) ) { Error( "Incomplete write, only ".$n_bytes." of ".length($tx_msg)." written: $!" ); } if ( $ack ) { Debug( "Waiting for ack" ); my $max_wait = 3; my $now = time(); while( 1 ) { my ( $count, $rx_msg ) = $self->{port}->read(4); if ( $count ) { #print( "Rx1: ".$count." bytes\n" ); my @resp = unpack( "C*", $rx_msg ); $self->printMsg( \@resp, "Rx" ); if ( $resp[0] = 0x80 + ($id<<4) ) { if ( ($resp[1] & 0xf0) == 0x40 ) { my $socket = $resp[1] & 0x0f; Debug( "Got ack for socket $socket" ); $result = !undef; } else { Error( "Got bogus response" ); } last; } else { Error( "Got message for camera ".(($resp[0]-0x80)>>4) ); } } if ( (time() - $now) > $max_wait ) { last; } } } if ( $cmp ) { Debug( "Waiting for command complete" ); my $max_wait = 10; my $now = time(); while( 1 ) { #print( "Waiting\n" ); my ( $count, $rx_msg ) = $self->{port}->read(16); if ( $count ) { #print( "Rx1: ".$count." bytes\n" ); my @resp = unpack( "C*", $rx_msg ); $self->printMsg( \@resp, "Rx" ); if ( $resp[0] = 0x80 + ($id<<4) ) { if ( ($resp[1] & 0xf0) == 0x50 ) { Debug( "Got command complete" ); $result = !undef; } else { Error( "Got bogus response" ); } last; } else { Error( "Got message for camera ".(($resp[0]-0x80)>>4) ); } } if ( (time() - $now) > $max_wait ) { last; } } } return( $result ); } sub cameraOff { my $self = shift; Debug( "Camera Off\n" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x00, 0x0, SYNC ); $self->sendCmd( \@msg ); } sub cameraOn { my $self = shift; Debug( "Camera On\n" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x00, 0x2, SYNC ); $self->sendCmd( \@msg ); } sub stop { my $self = shift; Debug( "Stop\n" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, 0x00, 0x00, 0x03, 0x03, SYNC ); $self->sendCmd( \@msg ); } sub moveConUp { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'tiltspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, 0x00, $speed, 0x03, 0x01, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveConDown { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'tiltspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, 0x00, $speed, 0x03, 0x02, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub movConLeft { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'panspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Left" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, $speed, 0x00, 0x01, 0x03, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveConRight { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'panspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Right" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, $speed, 0x00, 0x02, 0x03, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveUpLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up/Left" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, $panspeed, $tiltspeed, 0x01, 0x01, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveUpRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up/Right" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, $panspeed, $tiltspeed, 0x02, 0x01, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveDownLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down/Left" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, $panspeed, $tiltspeed, 0x01, 0x02, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveDownRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down/Right" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x01, $panspeed, $tiltspeed, 0x02, 0x02, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveRelUp { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); my $speed = $self->getParam( $params, 'tiltspeed', 0x40 ); Debug( "Step Up" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, 0x00, $speed, 0x00, 0x00, 0x00, 0x00, ($step&0xf000)>>12, ($step&0x0f00)>>8, ($step&0x00f0)>>4, ($step&0x000f)>>0, SYNC ); $self->sendCmd( \@msg ); } sub moveRelDown { my $self = shift; my $params = shift; my $step = -$self->getParam( $params, 'tiltstep' ); my $speed = $self->getParam( $params, 'tiltspeed', 0x40 ); Debug( "Step Down" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, 0x00, $speed, 0x00, 0x00, 0x00, 0x00, ($step&0xf000)>>12, ($step&0x0f00)>>8, ($step&0x00f0)>>4, ($step&0x000f)>>0, SYNC ); $self->sendCmd( \@msg ); } sub moveRelLeft { my $self = shift; my $params = shift; my $step = -$self->getParam( $params, 'panstep' ); my $speed = $self->getParam( $params, 'panspeed', 0x40 ); Debug( "Step Left" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, $speed, 0x00, ($step&0xf000)>>12, ($step&0x0f00)>>8, ($step&0x00f0)>>4, ($step&0x000f)>>0, 0x00, 0x00, 0x00, 0x00, SYNC ); $self->sendCmd( \@msg ); } sub moveRelRight { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); my $speed = $self->getParam( $params, 'panspeed', 0x40 ); Debug( "Step Right" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, $speed, 0x00, ($step&0xf000)>>12, ($step&0x0f00)>>8, ($step&0x00f0)>>4, ($step&0x000f)>>0, 0x00, 0x00, 0x00, 0x00, SYNC ); $self->sendCmd( \@msg ); } sub moveRelUpLeft { my $self = shift; my $params = shift; my $panstep = -$self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); Debug( "Step Up/Left" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, $panspeed, $tiltspeed, ($panstep&0xf000)>>12, ($panstep&0x0f00)>>8, ($panstep&0x00f0)>>4, ($panstep&0x000f)>>0, ($tiltstep&0xf000)>>12, ($tiltstep&0x0f00)>>8, ($tiltstep&0x00f0)>>4, ($tiltstep&0x000f)>>0, SYNC ); $self->sendCmd( \@msg ); } sub moveRelUpRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); Debug( "Step Up/Right" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, $panspeed, $tiltspeed, ($panstep&0xf000)>>12, ($panstep&0x0f00)>>8, ($panstep&0x00f0)>>4, ($panstep&0x000f)>>0, ($tiltstep&0xf000)>>12, ($tiltstep&0x0f00)>>8, ($tiltstep&0x00f0)>>4, ($tiltstep&0x000f)>>0, SYNC ); $self->sendCmd( \@msg ); } sub moveRelDownLeft { my $self = shift; my $params = shift; my $panstep = -$self->getParam( $params, 'panstep' ); my $tiltstep = -$self->getParam( $params, 'tiltstep' ); my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); Debug( "Step Down/Left" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, $panspeed, $tiltspeed, ($panstep&0xf000)>>12, ($panstep&0x0f00)>>8, ($panstep&0x00f0)>>4, ($panstep&0x000f)>>0, ($tiltstep&0xf000)>>12, ($tiltstep&0x0f00)>>8, ($tiltstep&0x00f0)>>4, ($tiltstep&0x000f)>>0, SYNC ); $self->sendCmd( \@msg ); } sub moveRelDownRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = -$self->getParam( $params, 'tiltstep' ); my $panspeed = $self->getParam( $params, 'panspeed', 0x40 ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x40 ); Debug( "Step Down/Right" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x03, $panspeed, $tiltspeed, ($panstep&0xf000)>>12, ($panstep&0x0f00)>>8, ($panstep&0x00f0)>>4, ($panstep&0x000f)>>0, ($tiltstep&0xf000)>>12, ($tiltstep&0x0f00)>>8, ($tiltstep&0x00f0)>>4, ($tiltstep&0x000f)>>0, SYNC ); $self->sendCmd( \@msg ); } sub zoomConTele { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x06 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Zoom Tele" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x07, 0x20|$speed, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->zoomStop(); } } sub zoomWide { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x06 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Zoom Wide" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x07, 0x30|$speed, SYNC ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->zoomStop(); } } sub zoomStop { my $self = shift; my $params = shift; Debug( "Zoom Stop" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x07, 0x00, SYNC ); $self->sendCmd( \@msg ); } sub focusConNear { my $self = shift; my $params = shift; Debug( "Focus Near" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x08, 0x03, SYNC ); $self->sendCmd( \@msg ); } sub focusConFar { my $self = shift; my $params = shift; Debug( "Focus Far" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x08, 0x02, SYNC ); $self->sendCmd( \@msg ); } sub focusStop { my $self = shift; my $params = shift; Debug( "Focus Stop" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x08, 0x00, SYNC ); $self->sendCmd( \@msg ); } sub focusAuto { my $self = shift; my $params = shift; Debug( "Focus Auto" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x38, 0x02, SYNC ); $self->sendCmd( \@msg ); } sub focusMan { my $self = shift; my $params = shift; Debug( "Focus Man" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x38, 0x03, SYNC ); $self->sendCmd( \@msg ); } sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Clear Preset $preset" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x3f, 0x00, $preset, SYNC ); $self->sendCmd( \@msg ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Set Preset $preset" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x3f, 0x01, $preset, SYNC ); $self->sendCmd( \@msg ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Goto Preset $preset" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x04, 0x3f, 0x02, $preset, SYNC ); $self->sendCmd( \@msg ); } sub presetHome { my $self = shift; my $params = shift; Debug( "Home Preset" ); my @msg = ( 0x80|$self->{Monitor}->{ControlAddress}, 0x01, 0x06, 0x04, SYNC ); $self->sendCmd( \@msg ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS3415.pm0000644000000000000000000001140613365153155023645 0ustar rootroot# ========================================================================== # # ZoneMinder D-Link DCS-3415 IP Control Protocol Module, 2018-03-04, 0.1 # Copyright (C) 2015-2018 Habib Kamei # # Modified for use with D-Link DCS-3415 IP Camera by Habib Kamei # 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. # # ========================================================================== # # This module contains the implementation of the D-Link DCS-3415 device control protocol # #=========================================================================== package ZoneMinder::Control::DCS3415; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # D-Link DCS-3415 Control Protocol # # On ControlAddress use the format : # USERNAME:PASSWORD@ADDRESS:PORT # eg : admin:@10.1.2.1:80 # zoneuser:zonepass@192.168.0.20:40000 # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub new { my $class = shift; my $id = shift; my $self = ZoneMinder::Control->new( $id ); bless( $self, $class ); srand( time() ); return $self; } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $class = ref( ) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return( $self->{$name} ); } Fatal( "Can't access $name member of object of class $class" ); } sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/cgi-bin/viewer/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed:'".$res->status_line()."'" ); } return( $result ); } #Zoom In sub Tele { my $self = shift; Debug( "Zoom Tele" ); my $cmd = "camctrl.cgi?channel=0&camid=1&zoom=tele"; $self->sendCmd( $cmd ); } #Zoom Out sub Wide { my $self = shift; Debug( "Zoom Wide" ); my $cmd = "camctrl.cgi?channel=0&camid=1&zoom=wide"; $self->sendCmd( $cmd ); } #Focus Near sub Near { my $self = shift; Debug( "Focus Near" ); my $cmd = "camctrl.cgi?channel=0&camid=1&focus=near"; $self->sendCmd( $cmd ); } #Focus Far sub Far { my $self = shift; Debug( "Focus Far" ); my $cmd = "camctrl.cgi?channel=0&camid=1&focus=far"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCAMIOS.pm0000644000000000000000000001550413365153155024126 0ustar rootroot# ========================================================================== # # ZoneMinder iPhone Control Protocol Module, $Date: 2018-07-15 00:20:00 +0000 $, $Revision: 0003 $ # Copyright (C) 2001-2008 Philip Coombes # # Modified for iPhone ipcamera for IOS BY PETER ZARGLIS n 2018-06-09 13:45:00 # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ========================================================================== # # This module contains the implementation of the iPhone ipcamera for IOS # control protocol. # # ========================================================================== package ZoneMinder::Control::IPCAMIOS; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # iPhone ipcamera for IOS Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); my $loopfactor=100000; sub new { my $class = shift; my $id = shift; my $self = ZoneMinder::Control->new( $id ); my $logindetails = ""; bless( $self, $class ); srand( time() ); return $self; } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return( $self->{$name} ); } Fatal( "Can't access $name member of object of class $class" ); } sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent" ); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = $res->decoded_content; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub getDisplayAttr { my $self = shift; my $param = shift; my $cmdget = "parameters?"; my $resp = $self->sendCmd( $cmdget ); my @fields = split(',',$resp); my $response=$fields[$param]; my @buffer=split(':',$response); my $response2=$buffer[1]; return ($response2); } sub sleep { } # Flip image vertically -> Horz -> off sub moveConUp { my $self = shift; my $params = shift; Debug( "Flip Image" ); my $dvalue=$self->getDisplayAttr(3); if ( $dvalue == 2 ) { $dvalue=0; my $cmd = "parameters?flip=$dvalue"; $self->sendCmd( $cmd ); } else { $dvalue=$dvalue+1; my $cmd = "parameters?flip=$dvalue"; $self->sendCmd( $cmd ); } } # Change camera (front facing or back) sub moveConDown { my $self = shift; my $params = shift; Debug( "Change Camera" ); my $dvalue=$self->getDisplayAttr(7); if ( $dvalue == 0 ) { my $cmd = "parameters?camera=1"; $self->sendCmd( $cmd ); } else { my $cmd = "parameters?camera=0"; $self->sendCmd( $cmd ); } } # Picture Orientation Clockwise sub moveConRight { my $self = shift; my $params = shift; Debug( "Orientation" ); my $dvalue=$self->getDisplayAttr(10); if ( $dvalue == 1 ) { $dvalue=4; my $cmd = "parameters?rotation=$dvalue"; $self->sendCmd( $cmd ); } else { $dvalue=$dvalue-1; my $cmd = "parameters?rotation=$dvalue"; $self->sendCmd( $cmd ); } } # Picture Orientation Anti-Clockwise sub moveConLeft { my $self = shift; my $params = shift; Debug( "Orientation" ); my $dvalue=$self->getDisplayAttr(10); if ( $dvalue == 4 ) { $dvalue=1; my $cmd = "parameters?rotation=$dvalue"; $self->sendCmd( $cmd ); } else { $dvalue=$dvalue+1; my $cmd = "parameters?rotation=$dvalue"; $self->sendCmd( $cmd ); } } # presetHome is used to turn off Torch, unlock Focus, unlock Exposure, unlock white-balance, rotation, image flipping # Just basically reset all the little variables and set it to medium quality # Rotation = 0 means it will autoselect using built in detection sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "parameters?torch=0&focus=0&wb=0&exposure=0&rotation=0&flip=0&quality=0.5"; $self->sendCmd( $cmd ); } sub focusAbsNear # Focus Un/Lock { my $self = shift; my $params = shift; Debug( "Focus Un/Lock" ); my $dvalue=$self->getDisplayAttr(2); if ( $dvalue == 0 ) { my $cmd = "parameters?focus=1"; $self->sendCmd( $cmd ); } else { my $cmd = "parameters?focus=0"; $self->sendCmd( $cmd ); } } sub focusAbsFar # Exposure Un/Lock { my $self = shift; my $params = shift; Debug( "Exposure Un/Lock" ); my $dvalue=$self->getDisplayAttr(11); if ( $dvalue == 0 ) { my $cmd = "parameters?exposure=1"; $self->sendCmd( $cmd ); } else { my $cmd = "parameters?exposure=0"; $self->sendCmd( $cmd ); } } # Increase stream Quality (from 0 to 10) sub irisAbsOpen { my $self = shift; my $params = shift; Debug( "Quality" ); my $dvalue=$self->getDisplayAttr(8); if ( $dvalue < 1 ) { $dvalue=$dvalue+0.1; my $cmd = "parameters?quality=$dvalue"; $self->sendCmd( $cmd ); } } # Decrease stream Quality (from 10 to 0) sub irisAbsClose { my $self = shift; my $params = shift; Debug( "Quality" ); my $dvalue=$self->getDisplayAttr(8); if ( $dvalue > 0 ) { $dvalue=$dvalue-0.1; my $cmd = "parameters?quality=$dvalue"; $self->sendCmd( $cmd ); } } # White Balance Un/Lock sub whiteAbsIn { my $self = shift; my $params = shift; Debug( "White Balance" ); my $dvalue=$self->getDisplayAttr(9); if ( $dvalue == 0 ) { my $cmd = "parameters?wb=1"; $self->sendCmd( $cmd ); } else { my $cmd = "parameters?wb=0"; $self->sendCmd( $cmd ); } } # Torch control on/off sub whiteAbsOut { my $self = shift; my $params = shift; Debug( "Torch" ); my $dvalue=$self->getDisplayAttr(5); if ( $dvalue == 0 ) { my $cmd = "parameters?torch=1"; $self->sendCmd( $cmd ); } else { my $cmd = "parameters?torch=0"; $self->sendCmd( $cmd ); } } 1; ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/WanscamHW0025.pm0000644000000000000000000002160613365153155025061 0ustar rootroot# ========================================================================== # # ZoneMinder Wanscam HW0025 IP Control Protocol Module, $Date: 2009-11-25 09:20:00 +0000 (Wed, 04 Nov 2009) $, $Revision: 0001 $ # Copyright (C) 2001-2015 Florian Neumair # Modified for use with Foscam FI8918W IP Camera by Dave Harris # Modified Feb 2011 by Howard Durdle (http://durdl.es/x) to: # fix horizontal panning, add presets and IR on/off # use Control Device field to pass username and password # # 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. # # ========================================================================== # # This module contains the implementation of the Wanscam HW0025 IP camera control # protocol # # Known working Devices: # * Wanscam HW0025 # * IPCC-7210W # package ZoneMinder::Control::WanscamHW0025; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Wanscam HW0025 IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); #print( "http://$address/$cmd\n" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} ); Info( "http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub cameraReset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } #Up Arrow sub moveConUp { my $self = shift; my $params = shift; Debug( "Move Up" ); my $cmd = "decoder_control.cgi?command=0&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Down Arrow sub moveConDown { my $self = shift; my $params = shift; Debug( "Move Down" ); my $cmd = "decoder_control.cgi?command=2&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Left Arrow sub moveConLeft { my $self = shift; my $params = shift; Debug( "Move Left" ); my $cmd = "decoder_control.cgi?command=4&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Right Arrow sub moveConRight { my $self = shift; my $params = shift; Debug( "Move Right" ); my $cmd = "decoder_control.cgi?command=6&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Up Right Arrow sub moveConUpRight { my $self = shift; my $params = shift; Debug( "Move Diagonally Up Right" ); my $cmd = "decoder_control.cgi?command=91&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Down Right Arrow sub moveConDownRight { my $self = shift; my $params = shift; Debug( "Move Diagonally Down Right" ); my $cmd = "decoder_control.cgi?command=93&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Up Left Arrow sub moveConUpLeft { my $self = shift; my $params = shift; Debug( "Move Diagonally Up Left" ); my $cmd = "decoder_control.cgi?command=90&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Diagonally Down Left Arrow sub moveConDownLeft { my $self = shift; my $params = shift; Debug( "Move Diagonally Down Left" ); my $cmd = "decoder_control.cgi?command=92&onestep=1&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "decoder_control.cgi?command=1&onestep=1&"; $self->sendCmd( $cmd ); } #Move Camera to Home Position sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "decoder_control.cgi?command=25&onestep=0&"; $self->sendCmd( $cmd ); } # zoom out sub zoomRelTele { my $self = shift; Debug( "Zoom Tele" ); my $cmd = "camera_control.cgi?param=17&value=1&"; $self->sendCmd( $cmd ); } #zoom in sub zoomRelWide { my $self = shift; Debug( "Zoom Wide" ); my $cmd = "camera_control.cgi?param=18&value=1&"; $self->sendCmd( $cmd ); } #Set preset sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 30 + (($preset-1)*2); Debug( "Set Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd&onestep=0&sit=$presetCmd&"; $self->sendCmd( $cmd ); } #Goto preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 31 + (($preset-1)*2); Debug( "Goto Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd&onestep=0&sit=$presetCmd&"; $self->sendCmd( $cmd ); } #Turn IR on sub wake { my $self = shift; Debug( "Wake - IR on" ); my $cmd = "camera_control.cgi?param=14&value=1&"; $self->sendCmd( $cmd ); } #Turn IR off sub sleep { my $self = shift; Debug( "Sleep - IR off" ); my $cmd = "camera_control.cgi?param=14&value=0&"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Control::Wanscam blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Keekoon.pm0000644000000000000000000001562613365153155024322 0ustar rootroot# ========================================================================== # # ZoneMinder Keekoon Control Protocol Module # This code was mostly derived from other ZM Control modules # # ========================================================================== # 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. # ========================================================================== # # Tested: KK002 (22 July 2016) # # Usage: # ====== # # Copy this file to say /usr/share/perl5/ZoneMinder/Control (Debian/Ubuntu) # # Create a new Control Capabilities: # Main: Name Keekoon, Type = Remote, Protocol = Keekoon # Move: Can Move, Can Move Diagonally, Can Move Continous # Pan: Can Pan # Tilt: Can Tilt # Presets: Has Presets, Num Presets = 6, Can Set Presets # # Set the ControlAddress in the camera definition, use the format: # http(s)://username:password@address:port # # eg : http://admin:adminpass@10.10.10.1:80 # or : https://admin:password@mycamera.example.co.uk:80 # # Return Location to Preset 1 # Auto Stop Timeout = 0.5 is a good starting point # # =========================================================================== # Problems: Enable debug and watch /tmp/zm_debug.log. The # correct debug log can be found by date stamp. # Enable/disable the Source for the camera in the web GUI # each time you edit this script. If the pid doesn't # change then you have not restarted it. # Errors like this: # [Error in response to Request:'400 URL must be absolute'] # means that you have not specified all the parts in ControlAddress or the # Regex has failed to parse it correctly # # ========================================================================= # Notes: # Example command from docs, at http://www.keekoonvision.com/for-developers-a: # Up: http://camera_ip:web_port/decoder_control.cgi?command=0&user=username&pwd=password # However the camera actually uses basic auth and not user= etc # # Test URLs with something like this # curl -XGET -u user:pass "http://cam.example.co.uk:80/decoder_control.cgi?command=1 # # These cameras have a default admin user but can have six more defined # with membership of three groups # https is not directly supported but could be via say HA Proxy, so that # is included rather than hardstrapping http:// # ========================================================================== package ZoneMinder::Control::Keekoon; use 5.006; use strict; use warnings; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; Info( "Open" ); } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; my ( $PROTOCOL, $USER, $PASS, $ADDR, $PORT ) = $self->{Monitor}->{ControlAddress} =~ /^(https?):\/\/(.*):(.*)@(.*):(\d+)$/; my $URL = $PROTOCOL."://".$ADDR.":".$PORT."/decoder_control.cgi?command=".$cmd; Debug( "ControlAddress from camera Control setting:".$self->{Monitor}->{ControlAddress} ); Debug( "URL parsed from ControlAddress:".$URL); my $req = HTTP::Request->new( GET=>$URL ); # Do Basic Auth $req->authorization_basic($USER, $PASS); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error in response to Request:'".$res->status_line()."'" ); } return( $result ); } # Set autoStop timeout on the Control tab for the camera sub autoStop { my $self = shift; my $stop_command = shift; my $autostop = shift; if( $stop_command && $autostop) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = $stop_command; $self->sendCmd( $cmd ); } } sub moveConUp { my $self = shift; my $cmd = "0"; my $stop_command = "1"; Debug( "Move Up" ); $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDown { my $self = shift; my $cmd = "2"; my $stop_command = "3"; Debug( "Move Down" ); $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } sub moveConLeft { my $self = shift; my $cmd = "4"; my $stop_command = "5"; Debug( "Move Left" ); $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } sub moveConRight { my $self = shift; my $cmd = "6"; my $stop_command = "7"; Debug( "Move Right" ); $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); $self->moveConUp( ); $self->moveConRight( ); } sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); $self->moveConDown( ); $self->moveConRight( ); } sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); $self->moveConUp( ); $self->moveConLeft( ); } sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); $self->moveConDown( ); $self->moveConLeft( ); } # SET: 30,32,34,36,38,40 for presets 1-6 sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset No: " . $preset ); if (( $preset >= 1 ) && ( $preset <= 6 )) { my $cmd = (($preset*2) + 28); $self->sendCmd( $cmd ); Debug( "Set preset cmd: " . $cmd ); } } # GOTO: 31,33,35,37,39,41 for presets 1-6 sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset No: " . $preset ); if (( $preset >= 1 ) && ( $preset <= 6 )) { my $cmd = (($preset*2) + 29); $self->sendCmd( $cmd ); Debug( "Goto Preset cmd: " . $cmd ); } } 1; ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoD.pm0000644000000000000000000004367313365153155024100 0ustar rootroot# ========================================================================== # # ZoneMinder Pelco-D Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the Pelco-D camera control # protocol # package ZoneMinder::Control::PelcoD; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Pelco-D Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use Time::HiRes qw( usleep ); use constant SYNC => 0xff; use constant COMMAND_GAP => 100000; # In ms sub open { my $self = shift; $self->loadMonitor(); use Device::SerialPort; $self->{port} = new Device::SerialPort( $self->{Monitor}->{ControlDevice} ); $self->{port}->baudrate(2400); $self->{port}->databits(8); $self->{port}->parity('none'); $self->{port}->stopbits(1); $self->{port}->handshake('none'); $self->{port}->read_const_time(50); $self->{port}->read_char_time(10); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; $self->{port}->close(); } sub printMsg { if ( logDebugging() ) { my $self = shift; my $msg = shift; my $prefix = shift || ""; $prefix = $prefix.": " if ( $prefix ); my $line_length = 16; my $msg_len = int(@$msg); my $msg_str = $prefix; for ( my $i = 0; $i < $msg_len; $i++ ) { if ( ($i > 0) && ($i%$line_length == 0) && ($i != ($msg_len-1)) ) { $msg_str .= sprintf( "\n%*s", length($prefix), "" ); } $msg_str .= sprintf( "%02x ", $msg->[$i] ); } $msg_str .= "[".$msg_len."]"; Debug( $msg_str ); } } sub sendCmd { my $self = shift; my $cmd = shift; my $ack = shift || 0; my $result = undef; my $checksum = 0x00; for ( my $i = 1; $i < int(@$cmd); $i++ ) { $checksum += $cmd->[$i]; $checksum &= 0xff; } push( @$cmd, $checksum ); $self->printMsg( $cmd, "Tx" ); my $id = $cmd->[0] & 0xf; my $tx_msg = pack( "C*", @$cmd ); #print( "Tx: ".length( $tx_msg )." bytes\n" ); my $n_bytes = $self->{port}->write( $tx_msg ); if ( !$n_bytes ) { Error( "Write failed: $!" ); } if ( $n_bytes != length($tx_msg) ) { Error( "Incomplete write, only ".$n_bytes." of ".length($tx_msg)." written: $!" ); } if ( $ack ) { Debug( "Waiting for ack" ); my $max_wait = 3; my $now = time(); while( 1 ) { my ( $count, $rx_msg ) = $self->{port}->read(4); if ( $count ) { #print( "Rx1: ".$count." bytes\n" ); my @resp = unpack( "C*", $rx_msg ); printMsg( \@resp, "Rx" ); if ( $resp[0] = 0x80 + ($id<<4) ) { if ( ($resp[1] & 0xf0) == 0x40 ) { my $socket = $resp[1] & 0x0f; Debug( "Got ack for socket $socket" ); $result = !undef; } else { Error( "Got bogus response" ); } last; } else { Error( "Got message for camera ".(($resp[0]-0x80)>>4) ); } } if ( (time() - $now) > $max_wait ) { Warning( "Response timeout" ); last; } } } } sub remoteReset { my $self = shift; Debug( "Remote Reset" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x0f, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub resetDefaults { my $self = shift; Debug( "Reset Defaults" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x29, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub cameraOff { my $self = shift; Debug( "Camera Off" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x08, 0x00, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub cameraOn { my $self = shift; Debug( "Camera On" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x88, 0x00, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub autoScan { my $self = shift; Debug( "Auto Scan" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x90, 0x00, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub manScan { my $self = shift; Debug( "Manual Scan" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x10, 0x00, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub stop { my $self = shift; Debug( "Stop" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x00, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub moveConUp { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'tiltspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x08, 0x00, $speed ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop( $params ); } } sub moveConDown { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'tiltspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x10, 0x00, $speed ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConLeft { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'panspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Left" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x04, $speed, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConRight { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'panspeed' ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Right" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x02, $speed, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConUpLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up/Left" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x0c, $panspeed, $tiltspeed ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConUpRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Up/Right" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x0a, $panspeed, $tiltspeed ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConDownLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down/Left" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x14, $panspeed, $tiltspeed ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveConDownRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed', 0x3f ); my $tiltspeed = $self->getParam( $params, 'tiltspeed', 0x3f ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Move Down/Right" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x12, $panspeed, $tiltspeed ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->stop(); } } sub moveStop { my $self = shift; Debug( "Move Stop" ); $self->stop(); } sub flip180 { my $self = shift; Debug( "Flip 180" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, 0x21 ); $self->sendCmd( \@msg ); } sub zeroPan { my $self = shift; Debug( "Zero Pan" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, 0x22 ); $self->sendCmd( \@msg ); } sub _setZoomSpeed { my $self = shift; my $speed = shift; Debug( "Set Zoom Speed $speed" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x25, 0x00, $speed ); $self->sendCmd( \@msg ); } sub zoomStop { my $self = shift; Debug( "Zoom Stop" ); $self->stop(); $self->_setZoomSpeed( 0 ); } sub zoomConTele { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x01 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Zoom Tele" ); $self->_setZoomSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x20, 0x00, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->zoomStop(); } } sub zoomConWide { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x01 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Zoom Wide" ); $self->_setZoomSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x40, 0x00, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->zoomStop(); } } sub _setFocusSpeed { my $self = shift; my $speed = shift; Debug( "Set Focus Speed $speed" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x27, 0x00, $speed ); $self->sendCmd( \@msg ); } sub focusConNear { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x03 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Focus Near" ); $self->_setFocusSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x01, 0x00, 0x00, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setFocusSpeed( 0 ); } } sub focusConFar { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed', 0x03 ); my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Focus Far" ); $self->_setFocusSpeed( $speed ); usleep( COMMAND_GAP ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x80, 0x00, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setFocusSpeed( 0 ); } } sub focusStop { my $self = shift; Debug( "Focus Stop" ); $self->stop(); $self->_setFocusSpeed( 0 ); } sub focusAuto { my $self = shift; Debug( "Focus Auto" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x2b, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub focusMan { my $self = shift; Debug( "Focus Man" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x2b, 0x00, 0x02 ); $self->sendCmd( \@msg ); } sub _setIrisSpeed { my $self = shift; my $speed = shift; Debug( "Set Iris Speed $speed" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x27, 0x00, $speed ); $self->sendCmd( \@msg ); } sub irisConClose { my $self = shift; my $params = shift; my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Iris Close" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x04, 0x00, 0x00, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setIrisSpeed( 0 ); } } sub irisConOpen { my $self = shift; my $params = shift; my $autostop = $self->getParam( $params, 'autostop', 0 ); Debug( "Iris Open" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x02, 0x80, 0x00, 0x00 ); $self->sendCmd( \@msg ); if( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->_setIrisSpeed( 0 ); } } sub irisStop { my $self = shift; Debug( "Iris Stop" ); $self->stop(); $self->_setIrisSpeed( 0 ); } sub irisAuto { my $self = shift; Debug( "Iris Auto" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x2d, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub irisMan { my $self = shift; Debug( "Iris Man" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x2d, 0x00, 0x02 ); $self->sendCmd( \@msg ); } sub writeScreen { my $self = shift; my $params = shift; my $string = $self->getParam( $params, 'string' ); Debug( "Writing '$string' to screen" ); my @chars = unpack( "C*", $string ); for ( my $i = 0; $i < length($string); $i++ ) { my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x15, $i, $chars[$i] ); $self->sendCmd( \@msg ); usleep( COMMAND_GAP ); } } sub clearScreen { my $self = shift; Debug( "Clear Screen" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x17, 0x00, 0x00 ); $self->sendCmd( \@msg ); } sub clearPreset { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Clear Preset $preset" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x05, 0x00, $preset ); $self->sendCmd( \@msg ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Set Preset $preset" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x03, 0x00, $preset ); $self->sendCmd( \@msg ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset', 1 ); Debug( "Goto Preset $preset" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, $preset ); $self->sendCmd( \@msg ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my @msg = ( SYNC, $self->{Monitor}->{ControlAddress}, 0x00, 0x07, 0x00, 0x22 ); $self->sendCmd( \@msg ); } sub reset { my $self = shift; Debug( "Reset" ); $self->remoteReset(); $self->resetDefaults(); } sub wake { my $self = shift; Debug( "Wake" ); $self->cameraOn(); } sub sleep { my $self = shift; Debug( "Sleep" ); $self->cameraOff(); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Ncs370.pm0000644000000000000000000001104613365153155023674 0ustar rootroot# ========================================================================== # # ZoneMinder Neu-Fusion Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the Neu-Fusion NCS370 IP camera # control protocol # package ZoneMinder::Control::Ncs370; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Ncs370 IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( POST=>"http://".$self->{Monitor}->{ControlAddress}."/PANTILTCONTROL.CGI" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub moveConUp { my $self = shift; Debug( "Move Up" ); my $cmd = "PanSingleMoveDegree=1\nTiltSingleMoveDegree=1\nPanTiltSingleMove=1"; $self->sendCmd( $cmd ); } sub moveConDown { my $self = shift; Debug( "Move Down" ); my $cmd = "PanSingleMoveDegree=1\nTiltSingleMoveDegree=1\nPanTiltSingleMove=7"; $self->sendCmd( $cmd ); } sub moveConLeft { my $self = shift; Debug( "Move Left" ); my $cmd = "PanSingleMoveDegree=1\nTiltSingleMoveDegree=1\nPanTiltSingleMove=3"; $self->sendCmd( $cmd ); } sub moveConRight { my $self = shift; Debug( "Move Right" ); my $cmd = "PanSingleMoveDegree=1\nTiltSingleMoveDegree=1\nPanTiltSingleMove=5"; $self->sendCmd( $cmd ); } sub moveConUpRight { moveConUp(); moveConRight(); } sub moveConUpLeft { moveConUp(); moveConLeft(); } sub moveConDownRight { moveConDown(); moveConRight(); } sub moveConDownLeft { moveConDown(); moveConLeft(); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "PanSingleMoveDegree=1\nTiltSingleMoveDegree=1\nPanTiltSingleMove=4"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm0000644000000000000000000002317313365153155023745 0ustar rootrootpackage ZoneMinder::Control::Dahua; use 5.8.0; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $REALM = ''; our $USERNAME = ''; our $PASSWORD = ''; our $ADDRESS = ''; our $PROTOCOL = 'http://'; use Time::HiRes qw(usleep); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use ZoneMinder::Database qw(zmDbConnect); sub new { my $class = shift; my $id = shift; my $self = ZoneMinder::Control->new( $id ); bless( $self, $class ); srand( time() ); return $self; } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return( $self->{$name} ); } Fatal( "Can't access $name member of object of class $class" ); } sub open { my $self = shift; $self->loadMonitor(); # The Dahua camera firmware API supports the concept of having multiple # channels on a single IP controller. # As most cameras only have a single channel, and there is no similar # information model in Zoneminder, I'm hardcoding the first and default # channel "0", here. $self->{dahua_channel_number} = "0"; if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?https?:\/\/)?(?[^:@]+)?:?(?[^\/@]+)?@?(?
.*)$/ ) ) { $PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL}; $USERNAME = $+{USERNAME} if $+{USERNAME}; $PASSWORD = $+{PASSWORD} if $+{PASSWORD}; $ADDRESS = $+{ADDRESS} if $+{ADDRESS}; } else { Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress}); $ADDRESS = $self->{Monitor}->{ControlAddress}; } if ( !($ADDRESS =~ /:/) ) { Error('You generally need to also specify the port. I will append :80'); $ADDRESS .= ':80'; } use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION); $self->{state} = 'closed'; # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) Debug("sendCmd credentials control address:'".$ADDRESS ."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD ."'" ); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); # Detect REALM my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz"; my $req = HTTP::Request->new(GET=>$get_config_url); my $res = $self->{ua}->request($req); if ($res->is_success) { $self->{state} = 'open'; return; } if ( $res->status_line() eq '401 Unauthorized' ) { my $headers = $res->headers(); foreach my $k (keys %$headers) { Debug("Initial Header $k => $$headers{$k}"); } if ($$headers{'www-authenticate'}) { my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; if ($tokens =~ /\w+="([^"]+)"/i) { if ($REALM ne $1) { $REALM = $1; Debug("Changing REALM to '" . $REALM . "'"); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); my $req = HTTP::Request->new(GET=>$get_config_url); $res = $self->{ua}->request($req); if ($res->is_success()) { $self->{state} = 'open'; return; } Debug('Authentication still failed after updating REALM' . $res->status_line); $headers = $res->headers(); foreach my $k ( keys %$headers ) { Debug("Initial Header $k => $$headers{$k}"); } # end foreach } else { Error('Authentication failed, not a REALM problem'); } } else { Error('Failed to match realm in tokens'); } # end if } else { Error('No WWW-Authenticate Header'); } # end if headers } # end if $res->status_line() eq '401 Unauthorized' } sub close { my $self = shift; $self->{state} = 'closed'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendGetRequest { my $self = shift; my $url_path = shift; my $result = undef; my $url = $PROTOCOL . $ADDRESS . $url_path; my $req = HTTP::Request->new(GET=>$url); my $res = $self->{ua}->request($req); if ($res->is_success) { $result = !undef; } else { if ($res->status_line() eq '401 Unauthorized') { Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD); Debug("Content was " . $res->content() ); my $res = $self->{ua}->request($req); if ($res->is_success) { $result = !undef; } else { Error("Content was " . $res->content() ); } } if ( ! $result ) { Error("Error check failed: '".$res->status_line()); } } return($result); } sub sendPtzCommand { my $self = shift; my $action = shift; my $command_code = shift; my $arg1 = shift; my $arg2 = shift; my $arg3 = shift; my $channel = $self->{dahua_channel_number}; my $url_path = "/cgi-bin/ptz.cgi?"; $url_path .= "action=" . $action . "&"; $url_path .= "channel=" . $channel . "&"; $url_path .= "code=" . $command_code . "&"; $url_path .= "arg1=" . $arg1 . "&"; $url_path .= "arg2=" . $arg2 . "&"; $url_path .= "arg3=" . $arg3; $self->sendGetRequest($url_path); } sub sendMomentaryPtzCommand { my $self = shift; my $command_code = shift; my $arg1 = shift; my $arg2 = shift; my $arg3 = shift; my $duration_ms = shift; $self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); my $duration_ns = $duration_ms * 1000; usleep($duration_ns); $self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); } sub moveRelUpLeft { my $self = shift; Debug("Move Up Left"); $self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); } sub moveRelUp { my $self = shift; Debug("Move Up"); $self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500); } sub moveRelUpRight { my $self = shift; Debug("Move Up Right"); $self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); } sub moveRelLeft { my $self = shift; Debug("Move Left"); $self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500); } sub moveRelRight { my $self = shift; Debug("Move Right"); $self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500); } sub moveRelDownLeft { my $self = shift; Debug("Move Down Left"); $self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); } sub moveRelDown { my $self = shift; Debug("Move Down"); $self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500); } sub moveRelDownRight { my $self = shift; Debug("Move Down Right"); $self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); } sub zoomRelTele { my $self = shift; Debug("Zoom Relative Tele"); $self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); } sub zoomRelWide { my $self = shift; Debug("Zoom Relative Wide"); $self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); } sub presetClear { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); $self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); } sub presetSet { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); my $dbh = zmDbConnect(1); my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?'; my $sth = $dbh->prepare($sql) or Fatal("Can't prepare sql '$sql': " . $dbh->errstr()); my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id) or Fatal("Can't execute sql '$sql': " . $sth->errstr()); my $control_preset_row = $sth->fetchrow_hashref(); my $new_label_name = $control_preset_row->{'Label'}; $self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); $self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); } sub presetGoto { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); $self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); } 1; __END__ =head1 NAME ZoneMinder::Control::Dahua - Perl module for Dahua cameras =head1 SYNOPSIS use ZoneMinder::Control::Dahua; place this in /usr/share/perl5/ZoneMinder/Control =head1 DESCRIPTION This module is an implementation of the Dahua IP camera HTTP control API. =head2 EXPORT None by default. =head1 COPYRIGHT AND LICENSE Copyright (C) 2018 ZoneMinder LLC This library 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 library 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. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Wanscam.pm0000644000000000000000000003027013365153155024310 0ustar rootroot# ========================================================================== # # ZoneMinder Wanscam Control Protocol Module, $Date: 2009-11-25 09:20:00 +0000 (Wed, 04 Nov 2009) $, $Revision: 0001 $ # Copyright (C) 2001-2008 Philip Coombes # Modified for use with Foscam FI8918W IP Camera by Dave Harris # Modified Feb 2011 by Howard Durdle (http://durdl.es/x) to: # fix horizontal panning, add presets and IR on/off # use Control Device field to pass username and password # Modified June 5th, 2012 by Chris Bagwell to: # Rename to IPCAM since its common protocol with wide range of cameras. # Work with Logger module instead of Debug module. # Fix off-by-1 preset bug. # Support optional autostop timeout. # Add Zoom, Brightness, and Contrast support. # Modified July 7th, 2012 by Patrik Brander to: # Rename to Wanscam # Pan Left/Right switched # IR On/Off switched # Brightness Increase/Decrease in 16 steps # # # 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. # # ========================================================================== # # This module contains the implementation of the Wanscam camera control # protocol. # # This is a protocol shared by a wide range of affordable cameras that # appear to share similar reference design and software. Examples # include Foscam, Agasio, Wansview, etc. # # The basis for CGI based API can be found on internet by searching for # "IPCAM CGI SDK 2.1". Here is sample site that also developes replacement # firmware for some hardware versions. # # http://www.openipcam.com/files/Manuals/IPCAM%20CGI%20SDK%202.1.pdf # package ZoneMinder::Control::Wanscam; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Wanscam Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = $res->decoded_content; } else { Error( "Error check failed:'".$res->status_line()."'" ); } return( $result ); } # Turn IO on (can be internally wired to IR's) sub wake { my $self = shift; Debug( "Wake - IO on" ); my $cmd = "decoder_control.cgi?command=94&"; $self->sendCmd( $cmd ); } # Turn IO off (can be internally wired to IR's) sub sleep { my $self = shift; Debug( "Sleep - IO off" ); my $cmd = "decoder_control.cgi?command=95&"; $self->sendCmd( $cmd ); } sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } sub moveConUp { my $self = shift; my $params = shift; Debug( "Move Up" ); my $cmd = "decoder_control.cgi?command=0&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConDown { my $self = shift; my $params = shift; Debug( "Move Down" ); my $cmd = "decoder_control.cgi?command=2&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConRight { my $self = shift; my $params = shift; Debug( "Move Right" ); my $cmd = "decoder_control.cgi?command=4&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConLeft { my $self = shift; my $params = shift; Debug( "Move Left" ); my $cmd = "decoder_control.cgi?command=6&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConUpLeft { my $self = shift; my $params = shift; Debug( "Move Diagonally Up Left" ); my $cmd = "decoder_control.cgi?command=91&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConDownLeft { my $self = shift; my $params = shift; Debug( "Move Diagonally Down Left" ); my $cmd = "decoder_control.cgi?command=93&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConUpRight { my $self = shift; my $params = shift; Debug( "Move Diagonally Up Right" ); my $cmd = "decoder_control.cgi?command=90&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } sub moveConDownRight { my $self = shift; my $params = shift; Debug( "Move Diagonally Down Right" ); my $cmd = "decoder_control.cgi?command=92&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $self->moveStop( $params ); } } # command=1 is technically Up Stop but seems to work for all stops. sub moveStop { my $self = shift; Debug( "Move Stop" ); print("autostop\n"); my $cmd = "decoder_control.cgi?command=1&"; $self->sendCmd( $cmd ); } sub zoomConTele { my $self = shift; my $params = shift; Debug( "Zoom Tele" ); my $cmd = "decoder_control.cgi?command=16&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $cmd = "decoder_control.cgi?command=17&"; $self->sendCmd( $cmd ); } } sub zoomConWide { my $self = shift; my $params = shift; Debug( "Zoom Wide" ); my $cmd = "decoder_control.cgi?command=18&"; $self->sendCmd( $cmd ); my $autostop = $self->getParam( $params, 'autostop', 0 ); if ( $autostop && $self->{Monitor}->{AutoStopTimeout} ) { usleep( $self->{Monitor}->{AutoStopTimeout} ); $cmd = "decoder_control.cgi?command=19&"; $self->sendCmd( $cmd ); } } sub zoomConStop { my $self = shift; my $params = shift; Debug( "Zoom Stop" ); my $cmd = "decoder_control.cgi?command=17&"; $self->sendCmd( $cmd ); } # Increase Brightness sub irisAbsOpen { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); my $brightness = 100; my $cmd = "get_camera_params.cgi?"; my $resp = $self->sendCmd( $cmd ); $brightness = int($1) if ( $resp =~ m/var brightness=([0-9]*);/ ); $brightness += $step * 16; $brightness = 255 if ($brightness > 255); Debug( "Iris Open $brightness" ); $cmd = "camera_control.cgi?param=1&value=".$brightness."&"; $self->sendCmd( $cmd ); } # Decrease Brightness sub irisAbsClose { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); my $brightness = 100; my $cmd = "get_camera_params.cgi?"; my $resp = $self->sendCmd( $cmd ); $brightness = int($1) if ( $resp =~ m/var brightness=([0-9]*);/ ); $brightness -= $step * 16; $brightness = 0 if ($brightness < 0); Debug( "Iris Close $brightness" ); $cmd = "camera_control.cgi?param=1&value=".$brightness."&"; $self->sendCmd( $cmd ); } # Increase Contrast sub whiteAbsIn { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); my $contrast = 5; my $cmd = "get_camera_params.cgi?"; my $resp = $self->sendCmd( $cmd ); $contrast = int($1) if ( $resp =~ m/var contrast=([0-9]*);/ ); $contrast += $step; $contrast = 6 if ($contrast > 6); Debug( "White In $contrast" ); $cmd = "camera_control.cgi?param=2&value=".$contrast."&"; $self->sendCmd( $cmd ); } # Decrease Contrast sub whiteAbsOut { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); my $contrast = 5; my $cmd = "get_camera_params.cgi?"; my $resp = $self->sendCmd( $cmd ); $contrast = int($1) if ( $resp =~ m/var contrast=([0-9]*);/ ); $contrast -= $step; $contrast = 0 if ($contrast < 0); Debug( "White Out $contrast" ); $cmd = "camera_control.cgi?param=2&value=".$contrast."&"; $self->sendCmd( $cmd ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "decoder_control.cgi?command=25&"; $self->sendCmd( $cmd ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 30 + (($preset-1)*2); Debug( "Set Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd&"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = 31 + (($preset-1)*2); Debug( "Goto Preset $preset with cmd $presetCmd" ); my $cmd = "decoder_control.cgi?command=$presetCmd&"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Control::Wanscam blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm0000644000000000000000000002671613365153155024634 0ustar rootroot# ========================================================================== # # ZoneMinder HikVision Control Protocol Module # Copyright (C) 2016 Terry Sanders # # 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. # # ========================================================================== # # This module contains an implementation of the HikVision ISAPI camera control # protocol # package ZoneMinder::Control::HikVision; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # HiKVision ISAPI Control Protocol # # Set the following: # ControlAddress: username:password@camera_webaddress:port # ControlDevice: IP Camera Model # # ========================================================================== use ZoneMinder::Logger qw(:all); use Time::HiRes qw( usleep ); use LWP::UserAgent; use HTTP::Cookies; my $ChannelID = 1; # Usually... my $DefaultFocusSpeed = 50; # Should be between 1 and 100 my $DefaultIrisSpeed = 50; # Should be between 1 and 100 my ($user,$pass,$host,$port); sub open { my $self = shift; $self->loadMonitor(); # # Create a UserAgent for the requests # $self->{UA} = LWP::UserAgent->new(); $self->{UA}->cookie_jar( {} ); # # Extract the username/password host/port from ControlAddress # if ( $self->{Monitor}{ControlAddress} =~ /^([^:]+):([^@]+)@(.+)/ ) { # user:pass@host... $user = $1; $pass = $2; $host = $3; } elsif ( $self->{Monitor}{ControlAddress} =~ /^([^@]+)@(.+)/ ) { # user@host... $user = $1; $host = $2; } else { # Just a host $host = $self->{Monitor}{ControlAddress}; } # Check if it is a host and port or just a host if ( $host =~ /([^:]+):(.+)/ ) { $host = $1; $port = $2; } else { $port = 80; } # Save the credentials if ( defined($user) ) { $self->{UA}->credentials("$host:$port", $self->{Monitor}{ControlDevice}, $user, $pass); } # Save the base url $self->{BaseURL} = "http://$host:$port"; } sub PutCmd { my $self = shift; my $cmd = shift; my $content = shift; my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd"); if ( defined($content) ) { $req->content_type("application/x-www-form-urlencoded; charset=UTF-8"); $req->content('' . "\n" . $content); } my $res = $self->{UA}->request($req); unless( $res->is_success ) { # # The camera timeouts connections at short intervals. When this # happens the user agent connects again and uses the same auth tokens. # The camera rejects this and asks for another token but the UserAgent # just gives up. Because of this I try the request again and it should # succeed the second time if the credentials are correct. # if ( $res->code == 401 ) { $res = $self->{UA}->request($req); unless( $res->is_success ) { # # It has failed authentication. The odds are # that the user has set some parameter incorrectly # so check the realm against the ControlDevice # entry and send a message if different # my $auth = $res->headers->www_authenticate; foreach (split(/\s*,\s*/,$auth)) { if ( $_ =~ /^realm\s*=\s*"([^"]+)"/i ) { if ( $self->{Monitor}{ControlDevice} ne $1 ) { Warning("Control Device appears to be incorrect. Control Device should be set to \"$1\". Control Device currently set to \"$self->{Monitor}{ControlDevice}\"."); $self->{Monitor}{ControlDevice} = $1; $self->{UA}->credentials("$host:$port", $self->{Monitor}{ControlDevice}, $user, $pass); return PutCmd($self,$cmd,$content); } } } # # Check for username/password # if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { Info("Check username/password is correct"); } elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { Info("No password in Control Address. Should there be one?"); } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) { Info("Password but no username in Control Address."); } else { Info("Missing username and password in Control Address."); } Fatal($res->status_line); } } else { Fatal($res->status_line); } } # end unless res->is_success } # end sub putCmd # # The move continuous functions all call moveVector # with the direction to move in. This includes zoom # sub moveVector { my $self = shift; my $pandirection = shift; my $tiltdirection = shift; my $zoomdirection = shift; my $params = shift; my $command; # The ISAPI/PTZ command # Calculate autostop time my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; # Change from microseconds to milliseconds $duration = int($duration/1000); my $momentxml; if( $duration ) { $momentxml = "$duration"; $command = "ISAPI/PTZCtrl/channels/$ChannelID/momentary"; } else { $momentxml = ""; $command = "ISAPI/PTZCtrl/channels/$ChannelID/continuous"; } # Calculate movement speeds my $x = $pandirection * $self->getParam( $params, 'panspeed', 0 ); my $y = $tiltdirection * $self->getParam( $params, 'tiltspeed', 0 ); my $z = $zoomdirection * $self->getParam( $params, 'speed', 0 ); # Create the XML my $xml = "$x$y$z$momentxml"; # Send it to the camera $self->PutCmd($command,$xml); } sub moveStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); } sub moveConUp { $_[0]->moveVector( 0, 1, 0, splice(@_,1)); } sub moveConUpRight { $_[0]->moveVector( 1, 1, 0, splice(@_,1)); } sub moveConRight { $_[0]->moveVector( 1, 0, 0, splice(@_,1)); } sub moveConDownRight { $_[0]->moveVector( 1, -1, 0, splice(@_,1)); } sub moveConDown { $_[0]->moveVector( 0, -1, 0, splice(@_,1)); } sub moveConDownLeft { $_[0]->moveVector( -1, -1, 0, splice(@_,1)); } sub moveConLeft { $_[0]->moveVector( -1, 0, 0, splice(@_,1)); } sub moveConUpLeft { $_[0]->moveVector( -1, 1, 0, splice(@_,1)); } sub zoomConTele { $_[0]->moveVector( 0, 0, 1, splice(@_,1)); } sub zoomConWide { $_[0]->moveVector( 0, 0,-1, splice(@_,1)); } # # Presets including Home set and clear # sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam($params,'preset'); $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset/goto"); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam($params,'preset'); my $xml = "$preset"; $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset",$xml); } sub presetHome { my $self = shift; my $params = shift; $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/homeposition/goto"); } # # Focus controls all call Focus with a +/- speed # sub Focus { my $self = shift; my $speed = shift; my $xml = "$speed"; $self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/focus",$xml); } sub focusConNear { my $self = shift; my $params = shift; # Calculate autostop time my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; # Get the focus speed my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); $self->Focus(-$speed); if($duration) { usleep($duration); $self->moveStop($params); } } sub Near { my $self = shift; my $params = shift; $self->Focus(-$DefaultFocusSpeed); } sub focusAbsNear { my $self = shift; my $params = shift; # Get the focus speed my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); $self->Focus(-$speed); } sub focusRelNear { my $self = shift; my $params = shift; # Get the focus speed my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); $self->Focus(-$speed); } sub focusConFar { my $self = shift; my $params = shift; # Calculate autostop time my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; # Get the focus speed my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); $self->Focus($speed); if($duration) { usleep($duration); $self->moveStop($params); } } sub Far { my $self = shift; my $params = shift; $self->Focus($DefaultFocusSpeed); } sub focusAbsFar { my $self = shift; my $params = shift; # Get the focus speed my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); $self->Focus($speed); } sub focusRelFar { my $self = shift; my $params = shift; # Get the focus speed my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); $self->Focus($speed); } # # Iris controls all call Iris with a +/- speed # sub Iris { my $self = shift; my $speed = shift; my $xml = "$speed"; $self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/iris",$xml); } sub irisConClose { my $self = shift; my $params = shift; # Calculate autostop time my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; # Get the iris speed my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); $self->Iris(-$speed); if($duration) { usleep($duration); $self->moveStop($params); } } sub Close { my $self = shift; my $params = shift; $self->Iris(-$DefaultIrisSpeed); } sub irisAbsClose { my $self = shift; my $params = shift; # Get the iris speed my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); $self->Iris(-$speed); } sub irisRelClose { my $self = shift; my $params = shift; # Get the iris speed my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); $self->Iris(-$speed); } sub irisConOpen { my $self = shift; my $params = shift; # Calculate autostop time my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; # Get the iris speed my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); $self->Iris($speed); if($duration) { usleep($duration); $self->moveStop($params); } } sub Open { my $self = shift; my $params = shift; $self->Iris($DefaultIrisSpeed); } sub irisAbsOpen { my $self = shift; my $params = shift; # Get the iris speed my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); $self->Iris($speed); } sub irisRelOpen { my $self = shift; my $params = shift; # Get the iris speed my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); $self->Iris($speed); } # # reset (reboot) the device # sub reset { my $self = shift; $self->PutCmd("ISAPI/System/reboot"); } 1; __END__ ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/M8640.pm0000644000000000000000000002533713365153155023445 0ustar rootroot# ========================================================================== # # ZoneMinder M8640 API Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2014 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the M8640 API camera control # protocol # URL's are as follows: # /cgi-bin/getLed.cgi # /cgi-bin/setGPIO.cgi command:6, duration: 0|1 for led on/off # /cgi-bin/reboot.cgi # /cgi-bin/restore.cgi # package ZoneMinder::Control::M8640; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Axis V2 Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); use URI::Encode qw(); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); #print( "http://$address/$cmd\n" ); #my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $url; if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { $url = $self->{Monitor}->{ControlAddress} .'/cgi-bin/setGPIO.cgi?preventCache='.time ; } else { $url = 'http://'.$self->{Monitor}->{ControlAddress} .'/cgi-bin/setGPIO.cgi?preventCache='.time ; } # en dif Error("Url: $url $cmd"); my $uri = URI::Encode->new( { encode_reserved => 0 } ); my $encoded = $uri->encode( $cmd ); my $res = $self->{ua}->post( $url, Content=>"data=$encoded" ); if ( $res->is_success ) { Debug("Success (" . $res->as_string . ') code: ' . $res->code ); $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub cameraReset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "/axis-cgi/admin/restart.cgi"; $self->sendCmd( $cmd ); } sub Up { my $self = shift; Debug( "Move Up" ); $self->sendCmd( '{"command":1,"duration":250}' ); } sub Down { my $self = shift; Debug( "Move Down" ); $self->sendCmd( '{"command":2,"duration":250}' ); } sub Left { my $self = shift; Debug( "Move Left" ); $self->sendCmd( '{"command":4,"duration":250}' ); } sub Right { my $self = shift; Debug( "Move Right" ); $self->sendCmd( '{"command":3,"duration":250}' ); } sub UpRight { my $self = shift; Debug( "Move Up/Right" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=upright"; $self->sendCmd( $cmd ); } sub UpLeft { my $self = shift; Debug( "Move Up/Left" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=upleft"; $self->sendCmd( $cmd ); } sub DownRight { my $self = shift; Debug( "Move Down/Right" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=downright"; $self->sendCmd( $cmd ); } sub DownLeft { my $self = shift; Debug( "Move Down/Left" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=downleft"; $self->sendCmd( $cmd ); } sub moveMap { my $self = shift; my $params = shift; my $xcoord = $self->getParam( $params, 'xcoord' ); my $ycoord = $self->getParam( $params, 'ycoord' ); Debug( "Move Map to $xcoord,$ycoord" ); my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=" .$self->{Monitor}->{Width} ."&imageheight=" .$self->{Monitor}->{Height} ; $self->sendCmd( $cmd ); } sub moveRelUp { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=$step"; $self->sendCmd( $cmd ); } sub moveRelDown { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=-$step"; $self->sendCmd( $cmd ); } sub moveRelLeft { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); Debug( "Step Left $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$step"; $self->sendCmd( $cmd ); } sub moveRelRight { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); Debug( "Step Right $step" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$step"; $self->sendCmd( $cmd ); } sub moveRelUpRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up/Right $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelUpLeft { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up/Left $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelDownRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down/Right $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelDownLeft { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down/Left $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep"; $self->sendCmd( $cmd ); } sub zoomRelTele { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Tele" ); my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step"; $self->sendCmd( $cmd ); } sub zoomRelWide { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Wide" ); my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step"; $self->sendCmd( $cmd ); } sub focusRelNear { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Near" ); my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step"; $self->sendCmd( $cmd ); } sub focusRelFar { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Far" ); my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step"; $self->sendCmd( $cmd ); } sub focusAuto { my $self = shift; Debug( "Focus Auto" ); my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=on"; $self->sendCmd( $cmd ); } sub focusMan { my $self = shift; Debug( "Focus Manual" ); my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=off"; $self->sendCmd( $cmd ); } sub irisRelOpen { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Open" ); my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step"; $self->sendCmd( $cmd ); } sub irisRelClose { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Close" ); my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step"; $self->sendCmd( $cmd ); } sub irisAuto { my $self = shift; Debug( "Iris Auto" ); my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=on"; $self->sendCmd( $cmd ); } sub irisMan { my $self = shift; Debug( "Iris Manual" ); my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=off"; $self->sendCmd( $cmd ); } sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Clear Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?move=home"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/3S.pm0000644000000000000000000002772013365153155023212 0ustar rootroot# ========================================================================== # # ZoneMinder 3S API Control Protocol Module, $Date: 2014-11-12 08:00:00 +0300 (Tue, 21 Jun 2011) $, $Revision: 1 $ # Copyright (C) 2014 Juan Manuel Castro # # 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. # # ========================================================================== # # This module contains the implementation of the 3S camera control # protocol #Model: N5071 #Hardware Version: 00 #Firmware Version: V1.03_STD-1 #Firmware Build Time: Jun 19 2012 15:28:17 package ZoneMinder::Control::3S; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # 3S Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; #$self->{ua}->agent( "ZoneMinder Control Agent/".ZM_VERSION ); $self->{ua}->agent( "ZoneMinder Control Agent/" . ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); #print("http://".$self->{Monitor}->{ControlAddress}."/$cmd"); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub cameraReset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "/restart.cgi"; $self->sendCmd( $cmd ); } #Custom# #Move X or Y Axis sub Up { my $self = shift; Debug( "Move Up" ); my $cmd = "/ptz.cgi?move=up"; $self->sendCmd( $cmd ); } sub Down { my $self = shift; Debug( "Move Down" ); my $cmd = "/ptz.cgi?move=down"; $self->sendCmd( $cmd ); } sub Left { my $self = shift; Debug( "Move Left" ); my $cmd = "/ptz.cgi?move=left"; $self->sendCmd( $cmd ); } sub Right { my $self = shift; Debug( "Move Right" ); my $cmd = "/ptz.cgi?move=right"; $self->sendCmd( $cmd ); } ##Zoom sub Tele { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Tele" ); my $cmd = "/ptz.cgi?rzoom=$step"; $self->sendCmd( $cmd ); } sub Wide { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Wide" ); my $cmd = "/ptz.cgi?rzoom=-$step"; $self->sendCmd( $cmd ); } #Move X and Y Axis sub UpRight { my $self = shift; Debug( "Move Up/Right" ); my $cmd = "/ptz.cgi?move=upright"; $self->sendCmd( $cmd ); } sub UpLeft { my $self = shift; Debug( "Move Up/Left" ); my $cmd = "/ptz.cgi?move=upleft"; $self->sendCmd( $cmd ); } sub DownRight { my $self = shift; Debug( "Move Down/Right" ); my $cmd = "/ptz.cgi?move=downright"; $self->sendCmd( $cmd ); } sub DownLeft { my $self = shift; Debug( "Move Down/Left" ); my $cmd = "/ptz.cgi?move=downleft"; $self->sendCmd( $cmd ); } #Foco sub focusAuto { my $self = shift; Debug( "Focus Auto" ); my $cmd = "/ptz.cgi?Autofocus=on"; $self->sendCmd( $cmd ); } sub focusMan { my $self = shift; Debug( "Focus Manual" ); my $cmd = "/ptz.cgi?Autofocus=off"; $self->sendCmd( $cmd ); } sub Near { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Near" ); my $cmd = "/ptz.cgi?rfocus=-$step"; $self->sendCmd( $cmd ); } sub Far { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Far" ); my $cmd = "/ptz.cgi?rfocus=$step"; $self->sendCmd( $cmd ); } #Iris sub Open { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Open" ); my $cmd = "/ptz.cgi?riris=$step"; $self->sendCmd( $cmd ); } sub Close { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Close" ); my $cmd = "/ptz.cgi?riris=-$step"; $self->sendCmd( $cmd ); } #Custom# sub moveConUp { my $self = shift; Debug( "Move Up" ); my $cmd = "/ptz.cgi?move=up"; $self->sendCmd( $cmd ); } sub moveConDown { my $self = shift; Debug( "Move Down" ); my $cmd = "/ptz.cgi?move=down"; $self->sendCmd( $cmd ); } sub moveConLeft { my $self = shift; Debug( "Move Left" ); my $cmd = "/ptz.cgi?move=left"; $self->sendCmd( $cmd ); } sub moveConRight { my $self = shift; Debug( "Move Right" ); my $cmd = "/ptz.cgi?move=right"; $self->sendCmd( $cmd ); } sub moveConUpRight { my $self = shift; Debug( "Move Up/Right" ); my $cmd = "/ptz.cgi?move=upright"; $self->sendCmd( $cmd ); } sub moveConUpLeft { my $self = shift; Debug( "Move Up/Left" ); my $cmd = "/ptz.cgi?move=upleft"; $self->sendCmd( $cmd ); } sub moveConDownRight { my $self = shift; Debug( "Move Down/Right" ); my $cmd = "/ptz.cgi?move=downright"; $self->sendCmd( $cmd ); } sub moveConDownLeft { my $self = shift; Debug( "Move Down/Left" ); my $cmd = "/ptz.cgi?move=downleft"; $self->sendCmd( $cmd ); } sub moveRelUp { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up $step" ); my $cmd = "/ptz.cgi?tilt=$step"; $self->sendCmd( $cmd ); } sub moveRelDown { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down $step" ); my $cmd = "/ptz.cgi?tilt=-$step"; $self->sendCmd( $cmd ); } sub moveRelLeft { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); Debug( "Step Left $step" ); my $cmd = "/ptz.cgi?pan=-$step"; $self->sendCmd( $cmd ); } sub moveRelRight { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); Debug( "Step Right $step" ); my $cmd = "/ptz.cgi?pan=$step"; $self->sendCmd( $cmd ); } sub moveRelUpRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up/Right $tiltstep/$panstep" ); my $cmd = "/ptz.cgi?pan=$panstep&tilt=$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelUpLeft { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up/Left $tiltstep/$panstep" ); my $cmd = "/ptz.cgi?pan=-$panstep&tilt=$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelDownRight { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down/Right $tiltstep/$panstep" ); my $cmd = "/ptz.cgi?pan=$panstep&tilt=-$tiltstep"; $self->sendCmd( $cmd ); } sub moveRelDownLeft { my $self = shift; my $params = shift; my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down/Left $tiltstep/$panstep" ); my $cmd = "/ptz.cgi?pan=-$panstep&tilt=-$tiltstep"; $self->sendCmd( $cmd ); } sub zoomRelTele { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Tele" ); my $cmd = "/ptz.cgi?rzoom=$step"; $self->sendCmd( $cmd ); } sub zoomRelWide { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Wide" ); my $cmd = "/ptz.cgi?rzoom=-$step"; $self->sendCmd( $cmd ); } sub focusRelNear { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Near" ); my $cmd = "/ptz.cgi?rfocus=-$step"; $self->sendCmd( $cmd ); } sub focusRelFar { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Far" ); my $cmd = "/ptz.cgi?rfocus=$step"; $self->sendCmd( $cmd ); } sub focusAuto { my $self = shift; Debug( "Focus Auto" ); my $cmd = "/ptz.cgi?Autofocus=on"; $self->sendCmd( $cmd ); } sub focusMan { my $self = shift; Debug( "Focus Manual" ); my $cmd = "/ptz.cgi?Autofocus=off"; $self->sendCmd( $cmd ); } sub irisRelOpen { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Open" ); my $cmd = "/ptz.cgi?riris=$step"; $self->sendCmd( $cmd ); } sub irisRelClose { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Iris Close" ); my $cmd = "/ptz.cgi?riris=-$step"; $self->sendCmd( $cmd ); } sub irisAuto { my $self = shift; Debug( "Iris Auto" ); my $cmd = "/ptz.cgi?autoiris=on"; $self->sendCmd( $cmd ); } sub irisMan { my $self = shift; Debug( "Iris Manual" ); my $cmd = "/ptz.cgi?autoiris=off"; $self->sendCmd( $cmd ); } sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Clear Preset $preset" ); my $cmd = "/ptz.cgi?removeserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); my $cmd = "/ptz.cgi?gotoserverpresetno=$preset"; $self->sendCmd( $cmd ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "/ptz.cgi?move=home"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Juan Manuel Castro, Ejuanmanuel.castro@gmail.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2014 Juan Manuel Castro This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Reolink.pm0000644000000000000000000011444513365153155024331 0ustar rootroot# ========================================================================== # # ZoneMinder Reolink IP Control Protocol Module, Date: 2016-01-19 # Converted for use with Reolink IP Camera by Chris Swertfeger # Copyright (C) 2016 Chris Swertfeger # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ========================================================================== # # This module contains the first implementation of the Reolink IP camera control # protocol # package ZoneMinder::Control::Reolink; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our %CamParams = (); # ========================================================================== # # Reolink IP Control Protocol # This script sends ONVIF compliant commands and may work with other cameras # that require authentication # # The script was developed against a RLC-423 and RLC-420. # # Basic preset functions are supported, but more advanced features, which make # use of abnormally high preset numbers (ir lamp control, tours, pan speed, etc) # may or may not work. # # # On ControlAddress use the format : # USERNAME:PASSWORD@ADDRESS:PORT # eg : admin:pass@10.1.2.1:8899 # admin:password@10.0.100.1:8899 # # Use port 8000 by default for Reolink cameras # # Make sure and place a value in the Auto Stop Timeout field. # Recommend starting with a value of 1 second, and adjust accordingly. # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); use MIME::Base64; use Digest::SHA; use DateTime; my ($username,$password,$host,$port); sub open { my $self = shift; $self->loadMonitor(); # # Extract the username/password host/port from ControlAddress # if( $self->{Monitor}{ControlAddress} =~ /^([^:]+):([^@]+)@(.+)/ ) { # user:pass@host... $username = $1; $password = $2; $host = $3; } elsif( $self->{Monitor}{ControlAddress} =~ /^([^@]+)@(.+)/ ) { # user@host... $username = $1; $host = $2; } else { # Just a host $host = $self->{Monitor}{ControlAddress}; } # Check if it is a host and port or just a host if( $host =~ /([^:]+):(.+)/ ) { $host = $1; $port = $2; } else { $port = 80; } use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $msg = shift; my $content_type = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $server_endpoint = "http://".$host.":".$port."/$cmd"; my $req = HTTP::Request->new( POST => $server_endpoint ); $req->header('content-type' => $content_type); $req->header('Host' => $host.":".$port); $req->header('content-length' => length($msg)); $req->header('accept-encoding' => 'gzip, deflate'); $req->header('connection' => 'close'); $req->content($msg); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "After sending PTZ command, camera returned the following error:'".$res->status_line()."'" ); } return( $result ); } sub getCamParams { my $self = shift; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg = ''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/onvif/imaging"; my $req = HTTP::Request->new( POST => $server_endpoint ); $req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"'); $req->header('Host' => $host.":".$port); $req->header('content-length' => length($msg)); $req->header('accept-encoding' => 'gzip, deflate'); $req->header('connection' => 'Close'); $req->content($msg); my $res = $self->{ua}->request($req); if ( $res->is_success ) { # We should really use an xml or soap library to parse the xml tags my $content = $res->decoded_content; if ($content =~ /.*(.+)<\/tt:Brightness>.*/) { $CamParams{$1} = $2; } if ($content =~ /.*(.+)<\/tt:Contrast>.*/) { $CamParams{$1} = $2; } } else { Error( "Unable to retrieve camera image settings:'".$res->status_line()."'" ); } } #autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; my $autostop = shift; if( $autostop ) { Debug( "Auto Stop" ); my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg = ''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000truefalse'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; usleep( $autostop ); $self->sendCmd( $cmd, $msg, $content_type ); } } # Reset the Camera sub reset { Debug( "Camera Reset" ); my $self = shift; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $cmd = ""; my $msg = ''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.''; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"'; $self->sendCmd( $cmd, $msg, $content_type ); } #Up Arrow sub moveConUp { Debug( "Move Up" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Down Arrow sub moveConDown { Debug( "Move Down" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Left Arrow sub moveConLeft { Debug( "Move Left" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Right Arrow sub moveConRight { Debug( "Move Right" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Zoom In sub zoomConTele { Debug( "Zoom Tele" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Zoom Out sub zoomConWide { Debug( "Zoom Wide" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpRight { Debug( "Move Diagonally Up Right" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownRight { Debug( "Move Diagonally Down Right" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpLeft { Debug( "Move Diagonally Up Left" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownLeft { Debug( "Move Diagonally Down Left" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Stop sub moveStop { Debug( "Move Stop" ); my $self = shift; my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000truefalse'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd( $cmd, $msg, $content_type ); } #Set Camera Preset sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'.$preset.''; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"'; $self->sendCmd( $cmd, $msg, $content_type ); } #Recall Camera Preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $num = sprintf("%03d", $preset); $num=~ tr/ /0/; Debug( "Goto Preset $preset" ); my $cmd = 'onvif/PTZ'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'.$num.''; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"'; $self->sendCmd( $cmd, $msg, $content_type ); } #Horizontal Patrol #To be determined if this camera supports this feature sub horizontalPatrol { Debug( "Horizontal Patrol" ); my $self = shift; my $cmd = ''; my $msg =''; my $content_type = ''; # $self->sendCmd( $cmd, $msg, $content_type ); Error( "PTZ Command not implemented in control script." ); } #Horizontal Patrol Stop #To be determined if this camera supports this feature sub horizontalPatrolStop { Debug( "Horizontal Patrol Stop" ); my $self = shift; my $cmd = ''; my $msg =''; my $content_type = ''; # $self->sendCmd( $cmd, $msg, $content_type ); Error( "PTZ Command not implemented in control script." ); } # Increase Brightness sub irisAbsOpen { Debug( "Iris $CamParams{'Brightness'}" ); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'Brightness'}); my $step = $self->getParam( $params, 'step' ); my $max = 100; $CamParams{'Brightness'} += $step; $CamParams{'Brightness'} = $max if ($CamParams{'Brightness'} > $max); my $cmd = 'onvif/imaging'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'.$CamParams{'Brightness'}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; $self->sendCmd( $cmd, $msg, $content_type ); } # Decrease Brightness sub irisAbsClose { Debug( "Iris $CamParams{'Brightness'}" ); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); my $min = 0; $CamParams{'Brightness'} -= $step; $CamParams{'Brightness'} = $min if ($CamParams{'Brightness'} < $min); my $cmd = 'onvif/imaging'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'.$CamParams{'Brightness'}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; $self->sendCmd( $cmd, $msg, $content_type ); } # Increase Contrast sub whiteAbsIn { Debug( "Iris $CamParams{'Contrast'}" ); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'Contrast'}); my $step = $self->getParam( $params, 'step' ); my $max = 100; $CamParams{'Contrast'} += $step; $CamParams{'Contrast'} = $max if ($CamParams{'Contrast'} > $max); my $cmd = 'onvif/imaging'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'.$CamParams{'Contrast'}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; } # Decrease Contrast sub whiteAbsOut { Debug( "Iris $CamParams{'Contrast'}" ); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'Contrast'}); my $step = $self->getParam( $params, 'step' ); my $min = 0; $CamParams{'Contrast'} -= $step; $CamParams{'Contrast'} = $min if ($CamParams{'Contrast'} < $min); my $cmd = 'onvif/imaging'; my $nonce; for (0..20){$nonce .= chr(int(rand(254)));} my $mydate = DateTime->now()->iso8601().'Z'; my $sha = Digest::SHA->new(1); $sha->add($nonce.$mydate.$password); my $digest = encode_base64($sha->digest,""); my $msg =''.$username.''.$digest.''.encode_base64($nonce,"").''.$mydate.'000'.$CamParams{'Contrast'}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; } 1; ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm0000644000000000000000000005073513365153155024145 0ustar rootroot# ========================================================================== # # ZoneMinder Netcat IP Control Protocol Module, $Date: 2009-11-25 09:20:00 +0000 (Wed, 04 Nov 2009) $, $Revision: 0001 $ # Copyright (C) 2001-2008 Philip Coombes # Converted for use with Netcat IP Camera by Andrew Bauer (knnniggett@users.sourceforge.net) # # 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. # # ========================================================================== # # This module contains the first implementation of the Netcat IP camera control # protocol # package ZoneMinder::Control::Netcat; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our %CamParams = (); # ========================================================================== # # Netcat IP Control Protocol # This script sends ONVIF compliant commands and may work with other cameras # # The Netcat camera gladly accepts any command with or without authentication, # which prevented me from developing Onvif authentication in this control script. # # Basic preset functions are supported, but more advanced features, which make # use of abnormally high preset numbers (ir lamp control, tours, pan speed, etc) # may or may not work. # # # Possible future improvements (for anyone to improve upon): # - Onvif authentication # - Build the SOAP commands at runtime rather than use templates # - Implement previously mentioned advanced features # # Implementing the first two will require additional Perl modules, and adding # more dependencies to ZoneMinder is always a concern. # # On ControlAddress use the format : # ADDRESS:PORT # eg : 10.1.2.1:8899 # 10.0.100.1:8899 # # Use port 8899 for the Netcat camera # # Make sure and place a value in the Auto Stop Timeout field. # Recommend starting with a value of 1 second, and adjust accordingly. # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug($msg.'['.$msg_len.']'); } sub sendCmd { my $self = shift; my $cmd = shift; my $msg = shift; my $content_type = shift; my $result = undef; printMsg($cmd, 'Tx'); my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd; my $req = HTTP::Request->new(POST => $server_endpoint); $req->header('content-type' => $content_type); $req->header('Host' => $self->{Monitor}->{ControlAddress}); $req->header('content-length' => length($msg)); $req->header('accept-encoding' => 'gzip, deflate'); $req->header('connection' => 'Close'); $req->content($msg); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'"); } return $result; } sub getCamParams { my $self = shift; my $msg = '000'; my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/onvif/imaging'; my $req = HTTP::Request->new(POST => $server_endpoint); $req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"'); $req->header('Host' => $self->{Monitor}->{ControlAddress}); $req->header('content-length' => length($msg)); $req->header('accept-encoding' => 'gzip, deflate'); $req->header('connection' => 'Close'); $req->content($msg); my $res = $self->{ua}->request($req); if ( $res->is_success ) { # We should really use an xml or soap library to parse the xml tags my $content = $res->decoded_content; if ( $content =~ /.*(.+)<\/tt:Brightness>.*/ ) { $CamParams{$1} = $2; } if ( $content =~ /.*(.+)<\/tt:Contrast>.*/ ) { $CamParams{$1} = $2; } } else { Error("Unable to retrieve camera image settings:'".$res->status_line()."'"); } } #autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; my $autostop = shift; if ( $autostop ) { Debug('Auto Stop'); my $cmd = 'onvif/PTZ'; my $msg = '000truefalse'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; usleep($autostop); $self->sendCmd($cmd, $msg, $content_type); } } # Reset the Camera sub reset { Debug('Camera Reset'); my $self = shift; my $cmd = ''; my $msg = ''; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"'; $self->sendCmd($cmd, $msg, $content_type); } #Up Arrow sub moveConUp { Debug('Move Up'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Down Arrow sub moveConDown { Debug('Move Down'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Left Arrow sub moveConLeft { Debug('Move Left'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Right Arrow sub moveConRight { Debug('Move Right'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Zoom In sub zoomConTele { Debug('Zoom Tele'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Zoom Out sub zoomConWide { Debug('Zoom Wide'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpRight { Debug('Move Diagonally Up Right'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownRight { Debug('Move Diagonally Down Right'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpLeft { Debug('Move Diagonally Up Left'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownLeft { Debug('Move Diagonally Down Left'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Stop sub moveStop { Debug('Move Stop'); my $self = shift; my $cmd = 'onvif/PTZ'; my $msg ='000truefalse'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; $self->sendCmd($cmd, $msg, $content_type); } #Set Camera Preset sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam($params, 'preset'); Debug("Set Preset $preset"); my $cmd = 'onvif/PTZ'; my $msg ='000'.$preset.''; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"'; $self->sendCmd($cmd, $msg, $content_type); } #Recall Camera Preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam($params, 'preset'); Debug("Goto Preset $preset"); my $cmd = 'onvif/PTZ'; my $msg ='000'.$preset.''; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"'; $self->sendCmd( $cmd, $msg, $content_type ); } #Horizontal Patrol #To be determined if this camera supports this feature sub horizontalPatrol { Debug('Horizontal Patrol'); my $self = shift; my $cmd = ''; my $msg =''; my $content_type = ''; # $self->sendCmd( $cmd, $msg, $content_type ); Error('PTZ Command not implemented in control script.'); } #Horizontal Patrol Stop #To be determined if this camera supports this feature sub horizontalPatrolStop { Debug('Horizontal Patrol Stop'); my $self = shift; my $cmd = ''; my $msg =''; my $content_type = ''; # $self->sendCmd( $cmd, $msg, $content_type ); Error('PTZ Command not implemented in control script.'); } # Increase Brightness sub irisAbsOpen { Debug("Iris $CamParams{Brightness}"); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{Brightness}); my $step = $self->getParam($params, 'step'); my $max = 100; $CamParams{Brightness} += $step; $CamParams{Brightness} = $max if ($CamParams{Brightness} > $max); my $cmd = 'onvif/imaging'; my $msg ='000'.$CamParams{Brightness}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; $self->sendCmd( $cmd, $msg, $content_type ); } # Decrease Brightness sub irisAbsClose { Debug( "Iris $CamParams{Brightness}" ); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{brightness}); my $step = $self->getParam( $params, 'step' ); my $min = 0; $CamParams{Brightness} -= $step; $CamParams{Brightness} = $min if ($CamParams{Brightness} < $min); my $cmd = 'onvif/imaging'; my $msg ='000'.$CamParams{Brightness}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; $self->sendCmd( $cmd, $msg, $content_type ); } # Increase Contrast sub whiteAbsIn { Debug("Iris $CamParams{Contrast}"); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{Contrast}); my $step = $self->getParam( $params, 'step' ); my $max = 100; $CamParams{Contrast} += $step; $CamParams{Contrast} = $max if ($CamParams{Contrast} > $max); my $cmd = 'onvif/imaging'; my $msg ='000'.$CamParams{Contrast}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; } # Decrease Contrast sub whiteAbsOut { Debug("Iris $CamParams{Contrast}"); my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{Contrast}); my $step = $self->getParam($params, 'step'); my $min = 0; $CamParams{Contrast} -= $step; $CamParams{Contrast} = $min if ($CamParams{Contrast} < $min); my $cmd = 'onvif/imaging'; my $msg ='000'.$CamParams{Contrast}.'true'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; } 1; __END__ ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9831W.pm0000644000000000000000000005460013365153155023674 0ustar rootroot# Modified by PP to clean up user/auth dependencies inside the script # Also, you can specify your auth credentials in the Control tab of the monitor # In "ControlDevice" put in # usr=xxxx&pwd=xxx # where xxx is the auth credentials to your foscam camera # The Foscam CGI manual referred to was v1.0.10 # All other notices below may be stale # # ========================================================================== # # ZoneMinder FOSCAM version 1.0 API Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================================= # # This module FI8620_Y2k.pm contains the implementation of API camera control # For FOSCAM FI8620 Dome PTZ Camera (This cam support only H264 streaming) # V1.0 Le 09 AOUT 2013 - production usable for the script but not for the camera "reboot itself" # If you wan't to contact me i understand French and English, precise ZoneMinder in subject # My name is Christophe DAPREMONT my email is christophe_y2k@yahoo.fr # # ========================================================================================= # package ZoneMinder::Control::FI9831W; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # =================================================================================================================================== # # FI9821 FOSCAM PT H264 Control Protocol # with Firmware version V1.2.1.1 (latest at 09/08/2013) # based with the latest buggy CGI doc from FOSCAM ( http://foscam.us/forum/cgi-sdk-for-hd-camera-t6045.html ) # This IPCAM work under ZoneMinder V1.25 from alternative source of code # from this svn at https://svn.unixmedia.net/public/zum/trunk/zum/ # Many Thanks to "MASTERTHEKNIFE" for the excellent speed optimisation ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=17652 ) # And to "NEXTIME" for the recent source update and incredible plugins ( http://www.zoneminder.com/forums/viewtopic.php?f=9&t=20587 ) # And all people helping ZoneMinder dev. # # -FUNCTION: display on OSD # speed is progressive in function of where you click on arrow ========> # speed low=/ \=speed high # =================================================================================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); # Set $osd to "off" if you wan't disabled OSD i need to place this variable in another script because # this script is reload at every command ,if i want the button on/off (Focus MAN) for OSD works... # PP - changed this to off - it achieves OSD by renaming the Device and what happens is at times # it does not reset the name if a command fails. Net result: Your camera gets a name like "Move Left" which # I bet you won't like my $osd = "off"; my $cmd; sub new { my $class = shift; my $id = shift; my $self = ZoneMinder::Control->new( $id ); bless( $self, $class ); srand( time() ); return $self; } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return( $self->{$name} ); } Fatal( "Can't access $name member of object of class $class" ); } sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; #PP #$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{ua}->agent( "ZoneMinder Control Agent/" ); $self->{state} = 'open'; } sub close { my $self = shift; $self->{state} = 'closed'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $temps = time(); #PP - cleaned this up so it picks up the full auth from Control Devices my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/cgi-bin/CGIProxy.fcgi?cmd=".$cmd."&".$self->{Monitor}->{ControlDevice} ); #my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/cgi-bin/CGIProxy.fcgi?usr%3Dadmin%26pwd%3D".$self->{Monitor}->{ControlDevice}."%26cmd%3D".$cmd."%26".$temps ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } # PP - changed this to a system reboot. Its harmful to reset here. Settings may change # with different firmware versions. Better to make this a reboot and use the camera # interface to reset streams sub reset { my $self = shift; Debug ( "Reboot= setup camera FoscamHD" ); $cmd = "rebootSystem"; #my $cmd = "setOSDSetting%26isEnableTimeStamp%3D0%26isEnableDevName%3D1%26dispPos%3D0%26isEnabledOSDMask%3D0"; Info ("Sending reboot $cmd"); $self->sendCmd( $cmd ); # Setup For Stream=0 Resolution=720p Bandwidth=4M FPS=30 KeyFrameInterval/GOP=100 VBR=ON #$cmd = "setVideoStreamParam%26streamType%3D0%26resolution%3D0%26bitRate%3D4194304%26frameRate%3D30%26GOP%3D100%26isVBR%3D1"; #$self->sendCmd( $cmd ); # Setup For Infrared AUTO #$cmd = "setInfraLedConfig%26Mode%3D1"; #$self->sendCmd( $cmd ); # Reset image settings #$cmd = "resetImageSetting"; #$self->sendCmd( $cmd ); } sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "ptzStopRun"; $self->sendCmd( $cmd ); if ($osd eq "on") { $cmd = "setDevName%26devName%3D."; $self->sendCmd( $cmd ); $cmd = "setOSDSetting%26isEnableDevName%3D1"; $self->sendCmd( $cmd ); } } sub autoStop { my $self = shift; my $autostop = shift; if( $autostop ) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "ptzStopRun"; $self->sendCmd( $cmd ); } } sub moveConUp { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Up" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Up $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveUp"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDown { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalization if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Down" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Down $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveDown"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConLeft { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $panspeed > 4 ) { $panspeed = 4; } if ( $panspeed < 0 ) { $panspeed = 0; } Debug( "Move Left" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Left $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$panspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveLeft"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConRight { my $self = shift; my $params = shift; my $panspeed = $self->getParam( $params, 'panspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $panspeed = abs($panspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $panspeed > 4 ) { $panspeed = 4; } if ( $panspeed < 0 ) { $panspeed = 0; } Debug( "Move Right" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Right $panspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$panspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveRight"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConUpLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Up Left" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Up Left $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveTopLeft"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConUpRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Up Right" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Up Right $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveTopRight"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDownLeft { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Down Left" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Down Left $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveBottomLeft"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub moveConDownRight { my $self = shift; my $params = shift; my $tiltspeed = $self->getParam( $params, 'tiltspeed' ); # speed inverter 4-->0 , 3-->1 , 2-->2 , 1-->3 , 0-->4 $tiltspeed = abs($tiltspeed - 4); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $tiltspeed > 4 ) { $tiltspeed = 4; } if ( $tiltspeed < 0 ) { $tiltspeed = 0; } Debug( "Move Con Down Right" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DMove Down Right $tiltspeed"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D$tiltspeed"; $self->sendCmd( $cmd ); $cmd = "ptzMoveBottomRight"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub zoomConTele { my $self = shift; Debug( "Zoom-Tele=MANU IR LED ON" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DManual IR LED Switch ON"; $self->sendCmd( $cmd ); } my $cmd = "setInfraLedConfig%26mode%3D1"; $self->sendCmd( $cmd ); $cmd = "openInfraLed"; $self->sendCmd( $cmd ); } sub zoomConWide { my $self = shift; Debug( "Zoom-Wide=MANU IR LED OFF" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DManual IR LED Switch OFF"; $self->sendCmd( $cmd ); } my $cmd = "setInfraLedConfig%26mode%3D1"; $self->sendCmd( $cmd ); $cmd = "closeInfraLed"; $self->sendCmd( $cmd ); } sub wake { my $self = shift; Debug( "Wake=AUTO IR LED" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DAuto IR LED Mode"; $self->sendCmd( $cmd ); } my $cmd = "setInfraLedConfig%26mode%3D0"; $self->sendCmd( $cmd ); } sub focusConNear { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Focus Near=Sharpness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSharpness $speed"; $self->sendCmd( $cmd ); $cmd = "setOSDSetting%26isEnableDevName%3D1"; $self->sendCmd( $cmd ); } my $cmd = "setSharpness%26sharpness%3D$speed"; $self->sendCmd( $cmd ); # La variable speed ne fonctionne pas en paramรจtre du focus, alors je l'utilise pour dรฉfinir la durรฉe de la commande # le rรฉsulat est identique $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub focusConFar { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Focus Far" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSharpness $speed"; $self->sendCmd( $cmd ); } my $cmd = "setSharpness%26sharpness%3D$speed"; $self->sendCmd( $cmd ); # La variable speed ne fonctionne pas en paramรจtre du focus alors je l'utilise pour dรฉfinir la durรฉe de la commande # le rรฉsulat est identique $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub focusAuto { my $self = shift; Debug( "Focus Auto=Reset Sharpness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DReset Sharpness"; $self->sendCmd( $cmd ); } my $cmd = "setSharpness%26sharpness%3D10"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub focusMan { my $self = shift; Debug( "Focus Manu=Reset Sharpness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DFOSCAM FI9821W Script V1.0 By Christophe_y2k"; $self->sendCmd( $cmd ); } } sub whiteConIn { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConIn=brightness" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DBrightness $speed"; $self->sendCmd( $cmd ); } my $cmd = "setBrightness%26brightness%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteConOut { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "White ConOut=Contrast" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DContrast $speed"; $self->sendCmd( $cmd ); } my $cmd = "setContrast%26constrast%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteAuto { my $self = shift; Debug( "White Auto=Brightness Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DBrightness Reset"; $self->sendCmd( $cmd ); } my $cmd = "setBrightness%26brightness%3D50"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub whiteMan { my $self = shift; Debug( "White Manuel=Contrast Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DContrast Reset"; $self->sendCmd( $cmd ); } my $cmd = "setContrast%26constrast%3D44"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConOpen { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConOpen=Saturation" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSaturation $speed"; $self->sendCmd( $cmd ); } my $cmd = "setSaturation%26saturation%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisConClose { my $self = shift; my $params = shift; my $speed = $self->getParam( $params, 'speed' ); # Normalisation en cas de valeur erronรฉe dans la base de donnรฉes if ( $speed > 100 ) { $speed = 100; } if ( $speed < 0 ) { $speed = 0; } Debug( "Iris ConClose=Hue" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DHue $speed"; $self->sendCmd( $cmd ); } my $cmd = "setHue%26hue%3D$speed"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisAuto { my $self = shift; Debug( "Iris Auto=Saturation Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSaturation Reset"; $self->sendCmd( $cmd ); } my $cmd = "setSaturation%26saturation%3D30"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub irisMan { my $self = shift; Debug( "Iris Manuel=Hue Reset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DHue Reset"; $self->sendCmd( $cmd ); } my $cmd = "setHue%26hue%3D6"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 16 ) ) { Debug( "Clear Preset $preset" ); my $cmd = "ptzDeletePresetPoint%26name%3D$preset"; $self->sendCmd( $cmd ); Debug( "Set Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DSet Preset $preset"; $self->sendCmd( $cmd ); } $cmd = "ptzAddPresetPoint%26name%3D$preset"; $self->sendCmd( $cmd ); } } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); if ( ( $preset >= 1 ) && ( $preset <= 16 ) ) { Debug( "Goto Preset $preset" ); if ( $osd eq "on" ) { my $cmd = "setDevName%26devName%3DGoto Preset $preset"; $self->sendCmd( $cmd ); } my $cmd = "setPTZSpeed%26speed%3D0"; $self->sendCmd( $cmd ); $cmd = "ptzGotoPresetPoint%26name%3D$preset"; $self->sendCmd( $cmd ); } } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 FI9831W ZoneMinder::Database - Perl extension for FOSCAM FI9831W =head1 SYNOPSIS Control script for Foscam HD cameras. Tested on 9831W but should work on others too. =head1 DESCRIPTION Control script for Foscam HD cameras. Tested on 9831W but should work on others too. You need to set "usr=xxx&pwd=yyy" in the ControlDevice field of the control tab for that monitor. Auto TimeOut should be 1. Don't set it to less - processes start crashing :) =head2 EXPORT None by default. =head1 SEE ALSO =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm0000644000000000000000000001423613365153155023701 0ustar rootroot# ========================================================================== # # ZoneMinder Foscam FI8908W / FI8918W IP Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # Modified for use with Foscam FI8908W IP Camera by Dave Harris # # 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. # # ========================================================================== # package ZoneMinder::Control::FI8908W; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Foscam FI8908W IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub new { my $class = shift; my $id = shift; my $self = ZoneMinder::Control->new( $id ); bless( $self, $class ); srand( time() ); return $self; } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return( $self->{$name} ); } Fatal( "Can't access $name member of object of class $class" ); } sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; my ($user, $password) = split /:/, $self->{Monitor}->{ControlDevice}; if ( !defined $password ) { # If value of "Control device" does not consist of two parts, then only password is given and we fallback to default user: $password = $user; $user = 'admin'; } $cmd .= "user=$user&pwd=$password"; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."' for URL ".$req->uri() ); } return( $result ); } sub reset { my $self = shift; Debug( "Camera Reset" ); $self->sendCmd( 'reboot.cgi?' ); } #Up Arrow sub moveConUp { my $self = shift; Debug( "Move Up" ); $self->sendCmd( 'decoder_control.cgi?command=0&' ); } #Down Arrow sub moveConDown { my $self = shift; Debug( "Move Down" ); $self->sendCmd( 'decoder_control.cgi?command=2&' ); } #Left Arrow sub moveConLeft { my $self = shift; Debug( "Move Left" ); $self->sendCmd( 'decoder_control.cgi?command=6&' ); } #Right Arrow sub moveConRight { my $self = shift; Debug( "Move Right" ); $self->sendCmd( 'decoder_control.cgi?command=4&' ); } #Diagonally Up Right Arrow sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); $self->sendCmd( 'decoder_control.cgi?command=90&' ); } #Diagonally Down Right Arrow sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); $self->sendCmd( 'decoder_control.cgi?command=92&' ); } #Diagonally Up Left Arrow sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); $self->sendCmd( 'decoder_control.cgi?command=91&' ); } #Diagonally Down Left Arrow sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); $self->sendCmd( 'decoder_control.cgi?command=93&' ); } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); $self->sendCmd( 'decoder_control.cgi?command=1&' ); } #Move Camera to Home Position sub presetHome { my $self = shift; Debug( "Home Preset" ); $self->sendCmd( 'decoder_control.cgi?command=25&' ); } sub moveRelUp { my $self = shift; Debug( "Move Up" ); $self->sendCmd( 'decoder_control.cgi?command=0&onestep=1&' ); } #Down Arrow sub moveRelDown { my $self = shift; Debug( "Move Down" ); $self->sendCmd( 'decoder_control.cgi?command=2&onestep=1&' ); } #Left Arrow sub moveRelLeft { my $self = shift; Debug( "Move Left" ); $self->sendCmd( 'decoder_control.cgi?command=6&onestep=1&' ); } #Right Arrow sub moveRelRight { my $self = shift; Debug( "Move Right" ); $self->sendCmd( 'decoder_control.cgi?command=4&onestep=1&' ); } #Go to preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $result = undef; if ( $preset > 0 && $preset <= 32 ) { my $command=31+(($preset-1) * 2); Debug( "Goto Preset $preset with command $command" ); $result=$self->sendCmd( 'decoder_control.cgi?command=' . $command . '&' ); } else { Error( "Unsupported preset $preset : must be between 1 and 32" ); } return $result; } 1; __END__ =pod =head1 NAME ZoneMinder::Control::FI8908W - Foscam FI8908W camera control =head1 DESCRIPTION This module contains the implementation of the Foscam FI8908W / FI8918W IP camera control protocol. The module uses "Control Device" value to retrieve user and password. User and password should be separated by colon, e.g. user:password. If colon is not provided, then "admin" is used as a fallback value for the user. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/PanasonicIP.pm0000644000000000000000000001514213365153155025064 0ustar rootroot# ========================================================================== # # ZoneMinder Panasonic IP Control Protocol Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the implementation of the Panasonic IP camera control # protocol # package ZoneMinder::Control::PanasonicIP; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Panasonic IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub cameraReset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "nphRestart?PAGE=Restart&Restart=OK"; $self->sendCmd( $cmd ); } sub moveConUp { my $self = shift; Debug( "Move Up" ); my $cmd = "nphControlCamera?Direction=TiltUp"; $self->sendCmd( $cmd ); } sub moveConDown { my $self = shift; Debug( "Move Down" ); my $cmd = "nphControlCamera?Direction=TiltDown"; $self->sendCmd( $cmd ); } sub moveConLeft { my $self = shift; Debug( "Move Left" ); my $cmd = "nphControlCamera?Direction=PanLeft"; $self->sendCmd( $cmd ); } sub moveConRight { my $self = shift; Debug( "Move Right" ); my $cmd = "nphControlCamera?Direction=PanRight"; $self->sendCmd( $cmd ); } sub moveMap { my $self = shift; my $params = shift; my $xcoord = $self->getParam( $params, 'xcoord' ); my $ycoord = $self->getParam( $params, 'ycoord' ); Debug( "Move Map to $xcoord,$ycoord" ); my $cmd = "nphControlCamera?Direction=Direct&NewPosition.x=$xcoord&NewPosition.y=$ycoord&Width=".$self->{Monitor}->{Width}."&Height=".$self->{Monitor}->{Height}; $self->sendCmd( $cmd ); } sub zoomConTele { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Tele" ); my $cmd = "nphControlCamera?Direction=ZoomTele"; $self->sendCmd( $cmd ); } sub zoomConWide { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Zoom Wide" ); my $cmd = "nphControlCamera?Direction=ZoomWide"; $self->sendCmd( $cmd ); } sub focusConNear { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Near" ); my $cmd = "nphControlCamera?Direction=FocusNear"; $self->sendCmd( $cmd ); } sub focusConFar { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'step' ); Debug( "Focus Far" ); my $cmd = "nphControlCamera?Direction=FocusFar"; $self->sendCmd( $cmd ); } sub focusAuto { my $self = shift; Debug( "Focus Auto" ); my $cmd = "nphControlCamera?Direction=FocusAuto"; $self->sendCmd( $cmd ); } sub focusMan { my $self = shift; Debug( "Focus Manual" ); my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=off"; $self->sendCmd( $cmd ); } sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Clear Preset $preset" ); my $cmd = "nphPresetNameCheck?Data=$preset"; $self->sendCmd( $cmd ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); my $cmd = "nphPresetNameCheck?PresetName=$preset&Data=$preset"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); my $cmd = "nphControlCamera?Direction=Preset&PresetOperation=Move&Data=$preset"; $self->sendCmd( $cmd ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "nphControlCamera?Direction=HomePosition"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/Toshiba_IK_WB11A.pm0000644000000000000000000001222613365153155025527 0ustar rootroot# ========================================================================== # # ZoneMinder Toshiba IK WB11A IP Camera Control Protocol Module, # Copyright (C) 2013 Tim Craig (timcraigNO@SPAMsonic.net) # # 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. # # ========================================================================== # # This module contains the implementation of the Airlink SkyIPCam # AICN747/AICN747W, TrendNet TV-IP410/TV-IP410W and other OEM versions of the # Fitivision CS-130A/CS-131A camera control protocol. # package ZoneMinder::Control::Toshiba_IK_WB11A; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Toshiba IK-WB11A IP Camera Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; #my $result = undef; printMsg( $cmd, "Tx" ); my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."$cmd" ); my $res = $self->{ua}->request($req); return( !undef ); } sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "/control.cgi?cont_2=16"; $self->sendCmd( $cmd ); } sub moveMap { Debug("MoveMap"); my $self = shift; my $params = shift; my $xcoord = $self->getParam( $params, 'xcoord' ); my $ycoord = $self->getParam( $params, 'ycoord' ); my $hor = $xcoord / $self->{Monitor}->{Width}; my $ver = $ycoord / $self->{Monitor}->{Height}; my $maxver = 10; my $maxhor = 10; my $horSteps = 0; my $verSteps = 0; $horSteps = $hor * $maxhor; $verSteps = $ver * $maxver; my $v = int($verSteps); my $h = int($horSteps); Debug( "Move Map to $xcoord,$ycoord, hor=$h, ver=$v"); my $cmd = "/cont.cgi?contptpoint_".$h."_".$v."=1"; $self->sendCmd( $cmd ); } sub moveRelUp { my $self = shift; Debug( "Step Up" ); my $cmd = "/control.cgi?cont_2=4"; $self->sendCmd( $cmd ); } sub moveRelDown { my $self = shift; Debug( "Step Down" ); my $cmd = "/control.cgi?cont_2=8"; $self->sendCmd( $cmd ); } sub moveRelLeft { my $self = shift; Debug( "Step Left" ); my $cmd = "/control.cgi?cont_2=1"; $self->sendCmd( $cmd ); } sub moveRelRight { my $self = shift; Debug( "Step Right" ); my $cmd = "/control.cgi?cont_2=2"; $self->sendCmd( $cmd ); } sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Clear Preset $preset" ); my $cmdNum = 3 << 8 | $preset; my $cmd = "/control.cgi?cont_4=$cmdNum"; $self->sendCmd( $cmd ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); my $cmdNum = 2 << 8 | $preset; my $cmd = "/control.cgi?cont_4=$cmdNum"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); my $cmdNum = 1 << 8 | $preset; my $cmd = "/control.cgi?cont_4=$cmdNum"; $self->sendCmd( $cmd ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Control::Toshiba_IK_WB11A - Zoneminder PTZ control module the Toshiba IK-WB11A IP Camera =head1 SYNOPSIS use ZoneMinder::Control::Toshiba_IK_WB11A; blah blah blah =head1 DESCRIPTION This is for Zoneminder PTZ control module for the Toshib_IK_WB11A camera. =head2 EXPORT None by default. =head1 SEE ALSO www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE Tim Craig, EtimcraigNO@SPAMsonic.netE =head1 COPYRIGHT AND LICENSE Copyright (C) 2013 by Tim Craig This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm0000644000000000000000000001505713365153155025014 0ustar rootroot# ========================================================================== # # ZoneMinder Airlink SkyIPCam AICN747/AICN747W Control Protocol Module, $Date: 2008-09-13 17:30:29 +0000 (Sat, 13 Sept 2008) $, $Revision: 2229 $ # Copyright (C) 2008 Brian Rudy (brudyNO@SPAMpraecogito.com) # # 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. # # ========================================================================== # # This module contains the implementation of the Airlink SkyIPCam # AICN747/AICN747W, TrendNet TV-IP410/TV-IP410W and other OEM versions of the # Fitivision CS-130A/CS-131A camera control protocol. # package ZoneMinder::Control::SkyIPCam7xx; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); # ========================================================================== # # Airlink SkyIPCam AICN747/AICN747W Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); sub open { my $self = shift; $self->loadMonitor(); use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); my $url; if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { $url = $self->{Monitor}->{ControlAddress}.$cmd; } else { $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; } # en dif my $req = HTTP::Request->new( GET=>$url ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed: '".$res->status_line()."'" ); } return( $result ); } sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "/admin/ptctl.cgi?move=reset"; $self->sendCmd( $cmd ); } sub moveMap { my $self = shift; my $params = shift; my $xcoord = $self->getParam( $params, 'xcoord' ); my $ycoord = $self->getParam( $params, 'ycoord' ); my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; my $maxver = 8; my $maxhor = 30; my $horDir = "right"; my $verDir = "up"; my $horSteps = 0; my $verSteps = 0; # Horizontal movement if ( $hor < 50 ) { # left $horSteps = ((50 - $hor) / 50) * $maxhor; $horDir = "left"; } elsif ( $hor > 50 ) { # right $horSteps = (($hor - 50) / 50) * $maxhor; $horDir = "right"; } # Vertical movement if ( $ver < 50 ) { # up $verSteps = ((50 - $ver) / 50) * $maxver; $verDir = "up"; } elsif ( $ver > 50 ) { # down $verSteps = (($ver - 50) / 50) * $maxver; $verDir = "down"; } my $v = int($verSteps); my $h = int($horSteps); Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; $self->sendCmd( $cmd ); $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; $self->sendCmd( $cmd ); } sub moveRelUp { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Up $step" ); my $cmd = "/admin/ptctl.cgi?move=up"; $self->sendCmd( $cmd ); } sub moveRelDown { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'tiltstep' ); Debug( "Step Down $step" ); my $cmd = "/admin/ptctl.cgi?move=down"; $self->sendCmd( $cmd ); } sub moveRelLeft { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); if ( $self->{Monitor}->{Orientation} eq "hori" ) { Debug( "Stepping Right because flipped horizontally " ); $self->sendCmd( "/admin/ptctl.cgi?move=right" ); } else { Debug( "Step Left" ); $self->sendCmd( "/admin/ptctl.cgi?move=left" ); } } sub moveRelRight { my $self = shift; my $params = shift; my $step = $self->getParam( $params, 'panstep' ); if ( $self->{Monitor}->{Orientation} eq "hori" ) { Debug( "Stepping Left because flipped horizontally " ); $self->sendCmd( "/admin/ptctl.cgi?move=left" ); } else { Debug( "Step Right" ); $self->sendCmd( "/admin/ptctl.cgi?move=right" ); } } sub presetClear { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Clear Preset $preset" ); #my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; #$self->sendCmd( $cmd ); } sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); $self->sendCmd( $cmd ); } sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "/admin/ptctl.cgi?move=h"; $self->sendCmd( $cmd ); } 1; __END__ =head1 NAME ZoneMinder::Control::SkyIPCam7xx.pm - Module for controlling AirLink101 SkyIPams =head1 SYNOPSIS use ZoneMinder::Control::SkyIPCam7xx; =head1 DESCRIPTION Module for controlling AirLink101 Cameras. =head2 EXPORT None by default. =head1 SEE ALSO ZoneMinder::Control =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE Brian Rudy, EbrudyNO@SPAMpraecogito.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2008 by Brian Rudy This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm0000644000000000000000000001721213365153155024545 0ustar rootroot# ========================================================================== # # ZoneMinder SunEyes SP-P1802SWPTZ IP Control Protocol Module, $Date: 2017-03-19 23:00:00 +1000 (Sat, 19 March 2017) $, $Revision: 0002 $ # Copyright (C) 2001-2008 Philip Coombes # Modified for use with Foscam FI8918W IP Camera by Dave Harris # Modified Feb 2011 by Howard Durdle (http://durdl.es/x) to: # fix horizontal panning, add presets and IR on/off # use Control Device field to pass username and password # Modified May 2014 by Arun Horne (http://arunhorne.co.uk) to: # use HTTP basic auth as required by firmware 11.37.x.x upward # Modified on Sep 28 2015 by Bobby Billingsley # Changes made # - Copied FI8918W.pm to SPP1802SWPTZ.pm # - modified to control a SunEyes SP-P1802SWPTZ # Modified on 13 March 2017 by Steve Gilvarry # -Address license and copyright issues # # 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. # # This module contains the implementation of the SunEyes SP-P1802SWPTZ IP # camera control protocol # package MyAgent; use base 'LWP::UserAgent'; package ZoneMinder::Control::SPP1802SWPTZ; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # SunEyes SP-P1802SWPTZ IP Control Protocol # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); our $stop_command; sub open { my $self = shift; $self->loadMonitor(); $self->{ua} = MyAgent->new; $self->{ua}->agent( "ZoneMinder Control Agent/" ); $self->{state} = 'open'; } sub printMsg { my $self = shift; my $msg = shift; my $msg_len = length($msg); Debug( $msg."[".$msg_len."]" ); } sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; printMsg( $cmd, "Tx" ); # PP Old cameras also support onstep=1 but it is too granular. Instead using moveCon and stop after interval # PP - cleaned up URL to take it properly from Control device # Control device needs to be of format user=xxx&pwd=yyy my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd"."&".$self->{Monitor}->{ControlDevice}); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error really, REALLY check failed:'".$res->status_line()."'" ); Error ("Cmd:".$cmd); } return( $result ); } sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } # PP - in all move operations, added auto stop after timeout #Up Arrow sub moveConUp { my $self = shift; Debug( "Move Up" ); my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=up"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Down Arrow sub moveConDown { my $self = shift; Debug( "Move Down" ); my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=down"; $self->sendCmd( $cmd ); } #Left Arrow sub moveConLeft { my $self = shift; Debug( "Move Left" ); my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=left"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Right Arrow sub moveConRight { my $self = shift; Debug( "Move Right" ); my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=right"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Right Arrow sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); foreach my $dir ("up","right") { my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=$dir"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } #Diagonally Down Right Arrow sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); foreach my $dir ("down","right") { my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=$dir"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } #Diagonally Up Left Arrow sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); foreach my $dir ("up","left") { my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=$dir"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } #Diagonally Down Left Arrow sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); foreach my $dir ("down","left") { my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=$dir"; $self->sendCmd( $cmd ); $self->autoStop( $self->{Monitor}->{AutoStopTimeout} ); } } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=stop"; $self->sendCmd( $cmd ); } # PP - imported from 9831 - autostop after usleep sub autoStop { my $self = shift; my $autostop = shift; if( $autostop ) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "cgi-bin/hi3510/ptzctrl.cgi?&-chn=0&-act=stop"; $self->sendCmd( $cmd ); } } #Move Camera to Home Position sub presetHome { my $self = shift; Debug( "Home Preset" ); my $cmd = "ptzgotopoint.cgi?&-chn=0&-point=1"; $self->sendCmd( $cmd ); } #Set preset sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = "cgi-bin/hi3510/ptzsetpoint.cgi?&-chn=0&-point=$preset"; Debug( "Set Preset $preset with cmd $presetCmd" ); my $cmd = $presetCmd; $self->sendCmd( $cmd ); } #Goto preset sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); my $presetCmd = "cgi-bin/hi3510/ptzgotopoint.cgi?&-chn=0&-point=$preset"; Debug( "Set Preset $preset with cmd $presetCmd" ); my $cmd = $presetCmd; $self->sendCmd( $cmd ); } #Turn IR on sub wake { my $self = shift; Debug( "Wake - IR on" ); my $cmd = "decoder_control.cgi?command=95"; $self->sendCmd( $cmd ); } #Turn IR off sub sleep { my $self = shift; Debug( "Sleep - IR off" ); my $cmd = "decoder_control.cgi?command=94"; $self->sendCmd( $cmd ); } 1; __END__ =head1 SPP1802SWPTZ ZoneMinder::Database - Perl extension for SunEyes SP-P1802SWPTZ =head1 SYNOPSIS Control script for SunEyes SP-P1802SWPTZ cameras. =head1 DESCRIPTION You can set "-speed=x" in the ControlDevice field of the control tab for that monitor. x should be an integer between 0 and 64 Auto TimeOut should be 1. Don't set it to less - processes start crashing :) =head2 EXPORT None by default. =head1 SEE ALSO =head1 AUTHOR Bobby Billingsley, Ebobby(at)bofh(dot)dkE based on the work of: Pliable Pixels, https://github.com/pliablepixels git checkout -b SunEyes_sp-p1802swptz =head1 COPYRIGHT AND LICENSE Copyright (C) 2015- Bobby Billingsley This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/0000755000000000000000000000000013365153155022342 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection.pm0000644000000000000000000001206113365153155024777 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Connection Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the base class definition of the trigger connection # class tree # package ZoneMinder::Trigger::Connection; use 5.006; use strict; use warnings; require ZoneMinder::Base; our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Base connection class # # ========================================================================== use ZoneMinder::Logger qw(:all); use Carp; our $AUTOLOAD; sub new { my $class = shift; my %params = @_; my $self = {}; $self->{name} = $params{name}; $self->{channel} = $params{channel}; $self->{input} = $params{mode} =~ /r/i; $self->{output} = $params{mode} =~ /w/i; bless( $self, $class ); return $self; } sub clone { my $self = shift; my $clone = { %$self }; bless $clone, ref $self; return( $clone ); } sub spawns { my $self = shift; return( $self->{channel}->spawns() ); } sub _spawn { my $self = shift; my $new_channel = shift; my $clone = $self->clone(); $clone->{channel} = $new_channel; return( $clone ); } sub accept { my $self = shift; my $new_channel = $self->{channel}->accept(); return( $self->_spawn( $new_channel ) ); } sub open { my $self = shift; return( $self->{channel}->open() ); } sub close { my $self = shift; return( $self->{channel}->close() ); } sub fileno { my $self = shift; return( $self->{channel}->fileno() ); } sub isOpen { my $self = shift; return( $self->{channel}->isOpen() ); } sub isConnected { my $self = shift; return( $self->{channel}->isConnected() ); } sub canRead { my $self = shift; return( $self->{input} && $self->isConnected() ); } sub canWrite { my $self = shift; return( $self->{output} && $self->isConnected() ); } sub getMessages { my $self = shift; my $buffer = $self->{channel}->read(); return( undef ) if ( !defined($buffer) ); my @messages = split( /\r?\n/, $buffer ); return( \@messages ); } sub putMessages { my $self = shift; my $messages = shift; if ( @$messages ) { my $buffer = join( "\n", @$messages ); $buffer .= "\n"; if ( !$self->{channel}->write( $buffer ) ) { Error( "Unable to write buffer '".$buffer ." to connection " .$self->{name} ." (" .$self->fileno() .")\n" ); } } return( undef ); } sub timedActions { } sub DESTROY { } sub AUTOLOAD { my $self = shift; my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return( $self->{$name} ); } elsif ( defined($self->{channel}) ) { if ( exists($self->{channel}->{$name}) ) { return( $self->{channel}->{$name} ); } } croak( "Can't access $name member of object of class $class" ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/0000755000000000000000000000000013365153155023712 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Serial.pm0000644000000000000000000001027213365153155025471 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Handle Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the class definition of the serial port trigger channel # class # package ZoneMinder::Trigger::Channel::Serial; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Trigger::Channel; our @ISA = qw(ZoneMinder::Trigger::Channel); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Serial access trigger channel # # ========================================================================== use ZoneMinder::Logger qw(:all); use Device::SerialPort; sub new { my $class = shift; my %params = @_; my $self = ZoneMinder::Trigger::Channel->new; $self->{path} = $params{path}; bless( $self, $class ); return $self; } sub open { my $self = shift; my $device = new Device::SerialPort( $self->{path} ); if ( ! $device ) { Error( "Unable to open $$self{path}: $!" ); $self->{state} = 'closed'; return; } $device->baudrate(9600); $device->databits(8); $device->parity('none'); $device->stopbits(1); $device->handshake('none'); $device->read_const_time(50); $device->read_char_time(10); $self->{device} = $device; $self->{state} = 'open'; $self->{state} = 'connected'; } sub close { my $self = shift; $self->{device}->close(); $self->{state} = 'closed'; } sub read { my $self = shift; my $buffer = $self->{device}->lookfor(); if ( !$buffer || !length($buffer) ) { return( undef ); } Debug( "Read '$buffer' (".length($buffer)." bytes)\n" ); return( $buffer ); } sub write { my $self = shift; my $buffer = shift; my $nbytes = $self->{device}->write( $buffer ); $self->{device}->write_drain(); if ( !defined( $nbytes) || $nbytes < length($buffer) ) { Error( "Unable to write buffer '".$buffer .", expected " .length($buffer) ." bytes, sent " .$nbytes .": $!\n" ); return( undef ); } Debug( "Wrote '$buffer' ($nbytes bytes)\n" ); return( !undef ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/File.pm0000644000000000000000000000644513365153155025140 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Handle Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the class definition of the simple file based trigger # channel class # package ZoneMinder::Trigger::Channel::File; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Trigger::Channel::Handle; our @ISA = qw(ZoneMinder::Trigger::Channel::Handle); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Simple file based trigger channel # # ========================================================================== use ZoneMinder::Logger qw(:all); use Carp; use Fcntl; sub new { my $class = shift; my %params = @_; my $self = ZoneMinder::Trigger::Channel::Handle->new; $self->{path} = $params{path}; bless( $self, $class ); return $self; } sub open { my $self = shift; local *sfh; #sysopen( *sfh, $conn->{path}, O_NONBLOCK|O_RDONLY ) or croak( "Can't sysopen: $!" ); #open( *sfh, "<".$conn->{path} ) or croak( "Can't open: $!" ); if ( ! open( *sfh, "+<", $self->{path} ) ) { Error( "Can't open file at $$self{path}: $!" ); croak( "Can't open file at $$self{path}: $!" ); } $self->{state} = 'open'; $self->{handle} = *sfh; } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Trigger::Channel::File - ZOneMinder object for a file based trigger channel =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Spawning.pm0000644000000000000000000000567413365153155026052 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Handle Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the class definition of the handle based trigger channel # classes that spawn new connections when connected. # package ZoneMinder::Trigger::Channel::Spawning; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Trigger::Channel::Handle; our @ISA = qw(ZoneMinder::Trigger::Channel::Handle); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Base class for handle based triggers that spawn new connections # # ========================================================================== use ZoneMinder::Logger qw(:all); sub new { my $class = shift; my $port = shift; my $self = ZoneMinder::Trigger::Channel::Handle->new(); $self->{spawns} = !undef; bless( $self, $class ); return $self; } sub spawns { return( !undef ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Handle.pm0000644000000000000000000000755313365153155025455 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Handle Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the class definition of the handle based trigger channel # class # package ZoneMinder::Trigger::Channel::Handle; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Trigger::Channel; our @ISA = qw(ZoneMinder::Trigger::Channel); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Base class for handle based trigger channels # # ========================================================================== use ZoneMinder::Logger qw(:all); use POSIX; sub new { my $class = shift; my $port = shift; my $self = ZoneMinder::Trigger::Channel->new(); $self->{handle} = undef; bless( $self, $class ); return $self; } sub spawns { return( undef ); } sub close { my $self = shift; close( $self->{handle} ); $self->{state} = 'closed'; $self->{handle} = undef; } sub read { my $self = shift; my $buffer; my $nbytes = sysread( $self->{handle}, $buffer, POSIX::BUFSIZ ); if ( !$nbytes ) { return( undef ); } Debug( "Read '$buffer' ($nbytes bytes)\n" ); return( $buffer ); } sub write { my $self = shift; my $buffer = shift; my $nbytes = syswrite( $self->{handle}, $buffer ); if ( !defined( $nbytes) || $nbytes < length($buffer) ) { Error( "Unable to write buffer '".$buffer .", expected " .length($buffer) ." bytes, sent " .($nbytes?$nbytes:'undefined') .": $!\n" ); return( undef ); } Debug( "Wrote '$buffer' ($nbytes bytes)\n" ); return( !undef ); } sub fileno { my $self = shift; return( defined($self->{handle})?fileno($self->{handle}):-1 ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Trigger::Channel::Handle - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Trigger::Channel::Handle; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Inet.pm0000644000000000000000000000747213365153155025161 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Handle Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the class definition of the tcp socket based trigger # channel class # package ZoneMinder::Trigger::Channel::Inet; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Trigger::Channel::Spawning; our @ISA = qw(ZoneMinder::Trigger::Channel::Spawning); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Internet (TCP) based trigger channel # # ========================================================================== use ZoneMinder::Logger qw(:all); use Carp; use Socket; sub new { my $class = shift; my %params = @_; my $self = ZoneMinder::Trigger::Channel::Spawning->new(); $self->{selectable} = !undef; $self->{port} = $params{port}; bless( $self, $class ); return $self; } sub open { my $self = shift; local *sfh; if ( ! socket( *sfh, PF_INET, SOCK_STREAM, getprotobyname('tcp') ) ) { Error( "Can't open socket: $!" ); croak( "Can't open socket: $!" ); } setsockopt( *sfh, SOL_SOCKET, SO_REUSEADDR, 1 ); my $saddr = sockaddr_in( $self->{port}, INADDR_ANY ); if ( ! bind( *sfh, $saddr ) ) { Error( "Can't bind to port $$self{port}: $!" ); croak( "Can't bind to port $$self{port}: $!" ); } if ( ! listen( *sfh, SOMAXCONN ) ) { Error( "Can't listen: $!" ); croak( "Can't listen: $!" ); } $self->{state} = 'open'; $self->{handle} = *sfh; } sub _spawn { my $self = shift; my $new_handle = shift; my $clone = $self->clone(); $clone->{handle} = $new_handle; $clone->{state} = 'connected'; return( $clone ); } sub accept { my $self = shift; local *cfh; my $paddr = accept( *cfh, $self->{handle} ); return( $self->_spawn( *cfh ) ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Trigger::Channel::Inet =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Unix.pm0000644000000000000000000000741013365153155025175 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Handle Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the class definition of the unix socket based trigger # channel class # package ZoneMinder::Trigger::Channel::Unix; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Trigger::Channel::Spawning; our @ISA = qw(ZoneMinder::Trigger::Channel::Spawning); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Unix socket based trigger channel # # ========================================================================== use ZoneMinder::Logger qw(:all); use Carp; use Socket; sub new { my $class = shift; my %params = @_; my $self = ZoneMinder::Trigger::Channel->new; $self->{selectable} = !undef; $self->{path} = $params{path}; bless( $self, $class ); return $self; } sub open { my $self = shift; local *sfh; unlink( $self->{path} ); my $saddr = sockaddr_un( $self->{path} ); if ( ! socket( *sfh, PF_UNIX, SOCK_STREAM, 0 ) ) { Error( "Can't open unix socket at $$self{path}: $!" ); croak( "Can't open unix socket at $$self{path}: $!" ); } if ( ! bind( *sfh, $saddr ) ) { Error( "Can't bind unix socket at $$self{path}: $!" ); croak( "Can't bind unix socket at $$self{path}: $!" ); } if ( ! listen( *sfh, SOMAXCONN ) ) { Error( "Can't listen: $!" ); croak( "Can't listen: $!" ); } $self->{handle} = *sfh; } sub _spawn { my $self = shift; my $new_handle = shift; my $clone = $self->clone(); $clone->{handle} = $new_handle; $clone->{state} = 'connected'; return( $clone ); } sub accept { my $self = shift; local *cfh; my $paddr = accept( *cfh, $self->{handle} ); return( $self->_spawn( *cfh ) ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Trigger::Channel::Unix - Object for Unix socket channel =head1 SYNOPSIS See zmtrigger.pl =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection/0000755000000000000000000000000013365153155024441 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection/Example.pm0000644000000000000000000000666613365153155026410 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Handle Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains an example overriden trigger connection class # package ZoneMinder::Trigger::Connection::Example; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Trigger::Connection; our @ISA = qw(ZoneMinder::Trigger::Connection); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Example overridden connection class # # ========================================================================== use ZoneMinder::Logger qw(:all); sub new { my $class = shift; my $path = shift; my $self = ZoneMinder::Trigger::Connection->new( @_ ); bless( $self, $class ); return $self; } sub getMessages { my $self = shift; my $buffer = $self->{channel}->read(); return( undef ) if ( !defined($buffer) ); Debug( "Handling buffer '$buffer'\n" ); my @messages = grep { s/-/|/g; 1; } split( /\r?\n/, $buffer ); return( \@messages ); } sub putMessages { my $self = shift; my $messages = shift; if ( @$messages ) { my $buffer = join( "\n", grep{ s/\|/-/; 1; } @$messages ); $buffer .= "\n"; if ( !$self->{channel}->write( $buffer ) ) { Error( "Unable to write buffer '".$buffer." to connection ".$self->{name}." (".$self->fileno().")\n" ); } } return( undef ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel.pm0000644000000000000000000000740413365153155024255 0ustar rootroot# ========================================================================== # # ZoneMinder Trigger Channel Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the base class definition of the trigger channel # class tree # package ZoneMinder::Trigger::Channel; use 5.006; use strict; use warnings; require ZoneMinder::Base; our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Database Access # # ========================================================================== use ZoneMinder::Logger qw(:all); use Carp; our $AUTOLOAD; sub new { my $class = shift; my $self = {}; $self->{readable} = !undef; $self->{writeable} = !undef; $self->{selectable} = undef; $self->{state} = 'closed'; bless( $self, $class ); return $self; } sub clone { my $self = shift; my $clone = { %$self }; bless $clone, ref $self; } sub open { my $self = shift; my $class = ref($self) or croak( "Can't get class for non object $self" ); croak( "Abstract base class method called for object of class $class" ); } sub close { my $self = shift; my $class = ref($self) or croak( "Can't get class for non object $self" ); croak( "Abstract base class method called for object of class $class" ); } sub getState { my $self = shift; return( $self->{state} ); } sub isOpen { my $self = shift; return( $self->{state} eq "open" ); } sub isConnected { my $self = shift; return( $self->{state} eq "connected" ); } sub DESTROY { } sub AUTOLOAD { my $self = shift; my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; if ( !exists($self->{$name}) ) { croak( "Can't access $name member of object of class $class" ); } return( $self->{$name} ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Object.pm0000644000000000000000000004232113365153155022505 0ustar rootroot# ========================================================================== # # ZoneMinder Object Module, $Date$, $Revision$ # Copyright (C) 2001-2017 ZoneMinder LLC # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Object; use 5.006; use strict; use warnings; require ZoneMinder::Base; our @ISA = qw(ZoneMinder::Base); # ========================================================================== # # General Utility Functions # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); use vars qw/ $AUTOLOAD $log $dbh %cache $no_cache/; *log = \$ZoneMinder::Logger::logger; *dbh = \$ZoneMinder::Database::dbh; my $debug = 0; $no_cache = 0; use constant DEBUG_ALL=>0; sub init_cache { $no_cache = 0; %cache = (); } # end sub init_cache sub new { my ( $parent, $id, $data ) = @_; $cache{$parent} = {} if ! $cache{$parent}; my $sub_cache = $cache{$parent}; my $self = {}; bless $self, $parent; no strict 'refs'; my $primary_key = ${$parent.'::primary_key'}; if ( ! $primary_key ) { Error( 'NO primary_key for type ' . $parent ); return; } # end if if ( $id and (!$no_cache) and $$sub_cache{$id} ) { if ( $data ) { # The reason to use load is if we have overriden it in the object, $$sub_cache{$id}->load( $data ); } return $$sub_cache{$id}; } if ( ( $$self{$primary_key} = $id ) or $data ) { #$log->debug("loading $parent $id") if $debug or DEBUG_ALL; $self->load( $data ); if ( !$no_cache ) { $$sub_cache{$id} = $self; } # end if } # end if return $self; } # end sub new sub load { my ( $self, $data ) = @_; my $type = ref $self; if ( ! $data ) { no strict 'refs'; my $table = ${$type.'::table'}; if ( ! $table ) { Error( 'NO table for type ' . $type ); return; } # end if my $primary_key = ${$type.'::primary_key'}; if ( ! $primary_key ) { Error( 'NO primary_key for type ' . $type ); return; } # end if if ( ! $$self{$primary_key} ) { my ( $caller, undef, $line ) = caller; Error( (ref $self) . "::load called without $primary_key from $caller:$line"); } else { #$log->debug("Object::load Loading from db $type"); Debug("Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); $data = $ZoneMinder::Database::dbh->selectrow_hashref( "SELECT * FROM $table WHERE $primary_key=?", {}, $$self{$primary_key} ); if ( ! $data ) { if ( $ZoneMinder::Database::dbh->errstr ) { Error( "Failure to load Object record for $$self{$primary_key}: Reason: " . $ZoneMinder::Database::dbh->errstr ); } else { Debug("No Results Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); } # end if } # end if } # end if } # end if ! $data if ( $data and %$data ) { @$self{keys %$data} = values %$data; } # end if return $data; } # end sub load sub lock_and_load { my ( $self ) = @_; my $type = ref $self; no strict 'refs'; my $table = ${$type.'::table'}; if ( ! $table ) { Error('NO table for type ' . $type); return; } # end if my $primary_key = ${$type.'::primary_key'}; if ( ! $primary_key ) { Error('NO primary_key for type ' . $type); return; } # end if if ( ! $$self{$primary_key} ) { my ( $caller, undef, $line ) = caller; Error("$type ::lock_and_load called without $primary_key from $caller:$line"); return; } Debug("Lock and Load $type from $table WHERE $primary_key = $$self{$primary_key}"); my $data = $ZoneMinder::Database::dbh->selectrow_hashref("SELECT * FROM $table WHERE $primary_key=? FOR UPDATE", {}, $$self{$primary_key}); if ( ! $data ) { if ( $ZoneMinder::Database::dbh->errstr ) { Error("Failure to load Object record for $$self{$primary_key}: Reason: " . $ZoneMinder::Database::dbh->errstr); } else { Debug("No Results Lock and Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); } # end if } # end if if ( $data and %$data ) { @$self{keys %$data} = values %$data; } else { Debug("No values Lock and Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); } # end if } # end sub lock_and_load sub AUTOLOAD { my ( $self, $newvalue ) = @_; my $type = ref($_[0]); my $name = $AUTOLOAD; $name =~ s/.*://; if ( @_ > 1 ) { return $_[0]{$name} = $_[1]; } return $_[0]{$name}; } sub save { my ( $self, $data, $force_insert ) = @_; my $type = ref $self; if ( ! $type ) { my ( $caller, undef, $line ) = caller; $log->error("No type in Object::save. self:$self from $caller:$line"); } my $local_dbh = eval '$'.$type.'::dbh'; $local_dbh = $ZoneMinder::Database::dbh if ! $local_dbh; $self->set( $data ? $data : {} ); if ( $debug or DEBUG_ALL ) { if ( $data ) { foreach my $k ( keys %$data ) { $log->debug("Object::save after set $k => $$data{$k} $$self{$k}"); } } } #$debug = 0; my $table = eval '$'.$type.'::table'; my $fields = eval '\%'.$type.'::fields'; my $debug = eval '$'.$type.'::debug'; #$debug = DEBUG_ALL if ! $debug; my %sql; foreach my $k ( keys %$fields ) { $sql{$$fields{$k}} = $$self{$k} if defined $$fields{$k}; } # end foreach if ( ! $force_insert ) { $sql{$$fields{updated_on}} = 'NOW()' if exists $$fields{updated_on}; } # end if my $serial = eval '$'.$type.'::serial'; my @identified_by = eval '@'.$type.'::identified_by'; my $ac = ZoneMinder::Database::start_transaction( $local_dbh ); if ( ! $serial ) { my $insert = $force_insert; my %serial = eval '%'.$type.'::serial'; if ( ! %serial ) { $log->debug("No serial") if $debug; # No serial columns defined, which means that we will do saving by delete/insert instead of insert/update if ( @identified_by ) { my $where = join(' AND ', map { $$fields{$_}.'=?' } @identified_by ); if ( $debug ) { $log->debug("DELETE FROM $table WHERE $where"); } # end if if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM $table WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) { $where =~ s/\?/\%s/g; $log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr); $local_dbh->rollback(); ZoneMinder::Database::end_transaction( $local_dbh, $ac ); return $local_dbh->errstr; } elsif ( $debug ) { $log->debug("SQL succesful DELETE FROM $table WHERE $where"); } # end if } # end if $insert = 1; } else { foreach my $id ( @identified_by ) { if ( ! $serial{$id} ) { my ( $caller, undef, $line ) = caller; $log->error("$id nor in serial for $type from $caller:$line") if $debug; next; } if ( ! $$self{$id} ) { my $s = qq{SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$table'}; ($$self{$id}) = ($sql{$$fields{$id}}) = $local_dbh->selectrow_array( $s ); #($$self{$id}) = ($sql{$$fields{$id}}) = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial{$id} . q{')} ); $log->debug("SQL statement execution SELECT $s returned $$self{$id}") if $debug or DEBUG_ALL; $insert = 1; } # end if } # end foreach } # end if ! %serial if ( $insert ) { my @keys = keys %sql; my $command = "INSERT INTO $table (" . join(',', @keys ) . ') VALUES (' . join(',', map { '?' } @sql{@keys} ) . ')'; if ( ! ( ( $_ = $local_dbh->prepare($command) ) and $_->execute( @sql{@keys} ) ) ) { my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr); $local_dbh->rollback(); ZoneMinder::Database::end_transaction( $local_dbh, $ac ); return $error; } # end if if ( $debug or DEBUG_ALL ) { $command =~ s/\?/\%s/g; $log->debug('SQL statement execution: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys} ) ).'):' ); } # end if } else { my @keys = keys %sql; my $command = "UPDATE $table SET " . join(',', map { $_ . ' = ?' } @keys ) . ' WHERE ' . join(' AND ', map { $_ . ' = ?' } @$fields{@identified_by} ); if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys,@$fields{@identified_by}} ) ) ) { my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr); $local_dbh->rollback(); ZoneMinder::Database::end_transaction( $local_dbh, $ac ); return $error; } # end if if ( $debug or DEBUG_ALL ) { $command =~ s/\?/\%s/g; $log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys,@$fields{@identified_by}} ) ).'):' ); } # end if } # end if } else { # not identified_by @identified_by = ('Id') if ! @identified_by; # If the size of the arrays are not equal which means one or more are missing my @identified_by_without_values = map { $$self{$_} ? () : $_ } @identified_by; my $need_serial = @identified_by_without_values > 0; if ( $force_insert or $need_serial ) { if ( $need_serial ) { if ( $serial ) { my $s = qq{SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$table'}; @$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( $s ); #@$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial . q{')} ); if ( $local_dbh->errstr() ) { $log->error("Error getting next id. " . $local_dbh->errstr() ); $log->error("SQL statement execution $s returned ".join(',',@$self{@identified_by})); } elsif ( $debug or DEBUG_ALL ) { $log->debug("SQL statement execution $s returned ".join(',',@$self{@identified_by})); } # end if } # end if } # end if my @keys = keys %sql; my $command = "INSERT INTO $table (" . join(',', @keys ) . ') VALUES (' . join(',', map { '?' } @sql{@keys} ) . ')'; if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys} ) ) ) { $command =~ s/\?/\%s/g; my $error = $local_dbh->errstr; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error); $local_dbh->rollback(); ZoneMinder::Database::end_transaction( $local_dbh, $ac ); return $error; } # end if if ( $debug or DEBUG_ALL ) { $command =~ s/\?/\%s/g; $log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys} ) ).'):' ); } # end if } else { delete $sql{created_on}; my @keys = keys %sql; my %identified_by = map { $_, $_ } @identified_by; @keys = map { $identified_by{$_} ? () : $$fields{$_} } @keys; my $command = "UPDATE $table SET " . join(',', map { $_ . ' = ?' } @keys ) . ' WHERE ' . join(' AND ', map { $$fields{$_} .'= ?' } @identified_by ); if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys}, @sql{@$fields{@identified_by}} ) ) ) { my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log; $local_dbh->rollback(); ZoneMinder::Database::end_transaction( $local_dbh, $ac ); return $error; } # end if if ( $debug or DEBUG_ALL ) { $command =~ s/\?/\%s/g; $log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? ( ref $_ eq 'ARRAY' ? join(',',@{$_}) : $_ ) : 'undef' } ( @sql{@keys}, @$self{@identified_by} ) ).'):' ); } # end if } # end if } # end if ZoneMinder::Database::end_transaction( $local_dbh, $ac ); $self->load(); #if ( $$fields{id} ) { #if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) { #$ZoneMinder::Object::cache{$type}{$$self{id}} = $self; #} # end if #delete $ZoneMinder::Object::cache{$config{db_name}}{$type}{$$self{id}}; #} # end if #$log->debug("after delete"); #eval 'if ( %'.$type.'::find_cache ) { %'.$type.'::find_cache = (); }'; #$log->debug("after clear cache"); return ''; } # end sub save sub set { my ( $self, $params ) = @_; my @set_fields = (); my $type = ref $self; my %fields = eval ('%'.$type.'::fields'); if ( ! %fields ) { $log->warn("ZoneMinder::Object::set called on an object ($type) with no fields".$@); } # end if my %defaults = eval('%'.$type.'::defaults'); if ( ref $params ne 'HASH' ) { my ( $caller, undef, $line ) = caller; $log->error("$type -> set called with non-hash params from $caller $line"); } foreach my $field ( keys %fields ) { if ( $params ) { $log->debug("field: $field, param: ".$$params{$field}) if $debug; if ( exists $$params{$field} ) { $log->debug("field: $field, $$self{$field} =? param: ".$$params{$field}) if $debug; if ( ( ! defined $$self{$field} ) or ($$self{$field} ne $params->{$field}) ) { # Only make changes to fields that have changed if ( defined $fields{$field} ) { $$self{$field} = $$params{$field} if defined $fields{$field}; push @set_fields, $fields{$field}, $$params{$field}; #mark for sql updating } # end if $log->debug("Running $field with $$params{$field}") if $debug; if ( my $func = $self->can( $field ) ) { $func->( $self, $$params{$field} ); } # end if } # end if } # end if } # end if $params if ( defined $fields{$field} ) { if ( $$self{$field} ) { $$self{$field} = transform( $type, $field, $$self{$field} ); } # end if $$self{field} } } # end foreach field foreach my $field ( keys %defaults ) { if ( ( ! exists $$self{$field} ) or (!defined $$self{$field}) or ( $$self{$field} eq '' ) ) { $log->debug("Setting default ($field) ($$self{$field}) ($defaults{$field}) ") if $debug; if ( defined $defaults{$field} ) { $log->debug("Default $field is defined: $defaults{$field}") if $debug; if ( $defaults{$field} eq 'NOW()' ) { $$self{$field} = 'NOW()'; } else { $$self{$field} = eval($defaults{$field}); $log->error( "Eval error of object default $field default ($defaults{$field}) Reason: " . $@ ) if $@; } # end if } else { $$self{$field} = $defaults{$field}; } # end if #$$self{$field} = ( defined $defaults{$field} ) ? eval($defaults{$field}) : $defaults{$field}; $log->debug("Setting default for ($field) using ($defaults{$field}) to ($$self{$field}) ") if $debug; } # end if } # end foreach default return @set_fields; } # end sub set sub transform { my $type = ref $_[0]; $type = $_[0] if ! $type; my $fields = eval '\%'.$type.'::fields'; my $value = $_[2]; if ( defined $$fields{$_[1]} ) { my @transforms = eval('@{$'.$type.'::transforms{$_[1]}}'); $log->debug("Transforms for $_[1] before $_[2]: @transforms") if $debug; if ( @transforms ) { foreach my $transform ( @transforms ) { if ( $transform =~ /^s\// or $transform =~ /^tr\// ) { eval '$value =~ ' . $transform; } elsif ( $transform =~ /^<(\d+)/ ) { if ( $value > $1 ) { $value = undef; } # end if } else { $log->debug("evalling $value ".$transform . " Now value is $value" ); eval '$value '.$transform; $log->error("Eval error $@") if $@; } $log->debug("After $transform: $value") if $debug; } # end foreach } # end if } else { $log->error("Object::transform ($_[1]) not in fields for $type"); } # end if return $value; } # end sub transform sub to_string { my $type = ref($_[0]); my $fields = eval '\%'.$type.'::fields'; return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } keys %$fields ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Object =head1 SYNOPSIS use parent ZoneMinder::Object; This package should likely not be used directly, as it is meant mainly to be a parent for all other ZoneMinder classes. =head1 DESCRIPTION A base Object to act as parent for other ZoneMinder Objects. =head2 EXPORT None by default. =head1 AUTHOR Isaac Connor, Eisaac@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2017 ZoneMinder LLC This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in0000644000000000000000000007145313365153155023164 0ustar rootroot# ========================================================================== # # ZoneMinder Memory Access Module, $Date: 2008-02-25 10:13:13 +0000 (Mon, 25 Feb 2008) $, $Revision: 2323 $ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Memory; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter ZoneMinder::Base); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( constants => [ qw( STATE_IDLE STATE_PREALARM STATE_ALARM STATE_ALERT STATE_TAPE ACTION_GET ACTION_SET ACTION_RELOAD ACTION_SUSPEND ACTION_RESUME TRIGGER_CANCEL TRIGGER_ON TRIGGER_OFF ) ], functions => [ qw( zmMemVerify zmMemInvalidate zmMemRead zmMemWrite zmMemTidy zmGetMonitorState zmGetAlarmLocation zmIsAlarmed zmInAlarm zmHasAlarmed zmGetStartupTime zmGetLastEvent zmGetLastWriteTime zmGetLastReadTime zmMonitorEnable zmMonitorDisable zmMonitorSuspend zmMonitorResume zmTriggerEventOn zmTriggerEventOff zmTriggerEventCancel zmTriggerShowtext ) ], ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Shared Memory Facilities # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use constant STATE_IDLE => 0; use constant STATE_PREALARM => 1; use constant STATE_ALARM => 2; use constant STATE_ALERT => 3; use constant STATE_TAPE => 4; use constant ACTION_GET => 1; use constant ACTION_SET => 2; use constant ACTION_RELOAD => 4; use constant ACTION_SUSPEND => 16; use constant ACTION_RESUME => 32; use constant TRIGGER_CANCEL => 0; use constant TRIGGER_ON => 1; use constant TRIGGER_OFF => 2; use Storable qw( freeze thaw ); if ( '@ENABLE_MMAP@' eq 'yes' ) { # 'yes' if memory is mmapped require ZoneMinder::Memory::Mapped; ZoneMinder::Memory::Mapped->import(); } else { require ZoneMinder::Memory::Shared; ZoneMinder::Memory::Shared->import(); } # Detaint our environment $ENV{PATH} = '/bin:/usr/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Native architecture # The following returned the wrong result on some 32 bit distros running on 64 bit hardware #our $arch = int(3.2*length(~0)); # New method for determining the bitness our $arch = 32 + 32*( qx(uname -m) =~ /64/ ); our $native = $arch/8; our $mem_seq = 0; our $mem_data = { shared_data => { type=>'SharedData', seq=>$mem_seq++, contents=> { size => { type=>'uint32', seq=>$mem_seq++ }, last_write_index => { type=>'uint32', seq=>$mem_seq++ }, last_read_index => { type=>'uint32', seq=>$mem_seq++ }, state => { type=>'uint32', seq=>$mem_seq++ }, last_event => { type=>'uint64', seq=>$mem_seq++ }, action => { type=>'uint32', seq=>$mem_seq++ }, brightness => { type=>'int32', seq=>$mem_seq++ }, hue => { type=>'int32', seq=>$mem_seq++ }, colour => { type=>'int32', seq=>$mem_seq++ }, contrast => { type=>'int32', seq=>$mem_seq++ }, alarm_x => { type=>'int32', seq=>$mem_seq++ }, alarm_y => { type=>'int32', seq=>$mem_seq++ }, valid => { type=>'uint8', seq=>$mem_seq++ }, active => { type=>'uint8', seq=>$mem_seq++ }, signal => { type=>'uint8', seq=>$mem_seq++ }, format => { type=>'uint8', seq=>$mem_seq++ }, imagesize => { type=>'uint32', seq=>$mem_seq++ }, epadding1 => { type=>'uint32', seq=>$mem_seq++ }, epadding2 => { type=>'uint32', seq=>$mem_seq++ }, startup_time => { type=>'time_t64', seq=>$mem_seq++ }, last_write_time => { type=>'time_t64', seq=>$mem_seq++ }, last_read_time => { type=>'time_t64', seq=>$mem_seq++ }, control_state => { type=>'uint8[256]', seq=>$mem_seq++ }, alarm_cause => { type=>'int8[256]', seq=>$mem_seq++ }, } }, trigger_data => { type=>'TriggerData', seq=>$mem_seq++, 'contents'=> { size => { type=>'uint32', seq=>$mem_seq++ }, trigger_state => { type=>'uint32', seq=>$mem_seq++ }, trigger_score => { type=>'uint32', seq=>$mem_seq++ }, padding => { type=>'uint32', seq=>$mem_seq++ }, trigger_cause => { type=>'int8[32]', seq=>$mem_seq++ }, trigger_text => { type=>'int8[256]', seq=>$mem_seq++ }, trigger_showtext => { type=>'int8[256]', seq=>$mem_seq++ }, } }, end => { seq=>$mem_seq++, size=>0 } }; our $mem_size = 0; sub zmMemInit { my $offset = 0; foreach my $section_data ( sort { $a->{seq} <=> $b->{seq} } values( %$mem_data ) ) { $section_data->{offset} = $offset; $section_data->{align} = 0; if ( $section_data->{align} > 1 ) { my $rem = $offset % $section_data->{align}; if ( $rem > 0 ) { $offset += ($section_data->{align} - $rem); } } foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) { if ( $member_data->{type} eq 'long' || $member_data->{type} eq 'ulong' || $member_data->{type} eq 'size_t' ) { $member_data->{size} = $member_data->{align} = $native; } elsif ( $member_data->{type} eq 'int64' || $member_data->{type} eq 'uint64' || $member_data->{type} eq 'time_t64' ) { $member_data->{size} = $member_data->{align} = 8; } elsif ( $member_data->{type} eq 'int32' || $member_data->{type} eq 'uint32' || $member_data->{type} eq 'bool4' ) { $member_data->{size} = $member_data->{align} = 4; } elsif ($member_data->{type} eq 'int16' || $member_data->{type} eq 'uint16' ) { $member_data->{size} = $member_data->{align} = 2; } elsif ( $member_data->{type} eq 'int8' || $member_data->{type} eq 'uint8' || $member_data->{type} eq 'bool1' ) { $member_data->{size} = $member_data->{align} = 1; } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { $member_data->{size} = $1; $member_data->{align} = 1; } else { Fatal( "Unexpected type '".$member_data->{type} ."' found in shared data definition." ); } if ( $member_data->{align} > 1 && ($offset%$member_data->{align}) > 0 ) { $offset += ($member_data->{align} - ($offset%$member_data->{align})); } $member_data->{offset} = $offset; $offset += $member_data->{size} } $section_data->{size} = $offset - $section_data->{offset}; } $mem_size = $offset; } &zmMemInit(); sub zmMemVerify { my $monitor = shift; if ( !zmMemAttach( $monitor, $mem_size ) ) { return( undef ); } my $sd_size = zmMemRead( $monitor, 'shared_data:size', 1 ); if ( $sd_size != $mem_data->{shared_data}->{size} ) { if ( $sd_size ) { Error( "Shared data size conflict in shared_data for monitor " .$monitor->{Name} .", expected " .$mem_data->{shared_data}->{size} .", got " .$sd_size ); } else { Debug( "Shared data size conflict in shared_data for monitor " .$monitor->{Name} .", expected " .$mem_data->{shared_data}->{size} .", got ".$sd_size ); } return( undef ); } my $td_size = zmMemRead( $monitor, 'trigger_data:size', 1 ); if ( $td_size != $mem_data->{trigger_data}->{size} ) { if ( $td_size ) { Error( "Shared data size conflict in trigger_data for monitor " .$monitor->{Name} .", expected " .$mem_data->{triggger_data}->{size} .", got " .$td_size ); } else { Debug( "Shared data size conflict in trigger_data for monitor " .$monitor->{Name} .", expected " .$mem_data->{triggger_data}->{size} .", got " .$td_size ); } return( undef ); } if ( !zmMemRead($monitor, 'shared_data:valid',1) ) { Error( "Shared data not valid for monitor $$monitor{Id}" ); return( undef ); } return( !undef ); } sub zmMemRead { my $monitor = shift; my $fields = shift; if ( !ref($fields) ) { $fields = [ $fields ]; } my @values; foreach my $field ( @$fields ) { my ( $section, $element ) = split( /[\/:.]/, $field ); Fatal( "Invalid shared data selector '$field'" ) if ( !$section || !$element ); my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; my $type = $mem_data->{$section}->{contents}->{$element}->{type}; my $size = $mem_data->{$section}->{contents}->{$element}->{size}; if (!defined $offset || !defined $type || !defined $size) { Error ("Invalid field:".$field." setting to undef and exiting zmMemRead"); zmMemInvalidate( $monitor ); return( undef ); } my $data = zmMemGet( $monitor, $offset, $size ); if ( !defined($data) ) { Error( "Unable to read '$field' from memory for monitor ".$monitor->{Id} ); zmMemInvalidate( $monitor ); return( undef ); } my $value; if ( $type eq 'long' ) { ( $value ) = unpack( 'l!', $data ); } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { ( $value ) = unpack( 'L!', $data ); } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { # The 'q' type is only available on 64bit platforms, so use native. ( $value ) = unpack( 'l!', $data ); } elsif ( $type eq 'uint64' ) { # The 'q' type is only available on 64bit platforms, so use native. ( $value ) = unpack( 'L!', $data ); } elsif ( $type eq 'int32' ) { ( $value ) = unpack( 'l', $data ); } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { ( $value ) = unpack( 'L', $data ); } elsif ( $type eq 'int16' ) { ( $value ) = unpack( 's', $data ); } elsif ( $type eq 'uint16' ) { ( $value ) = unpack( 'S', $data ); } elsif ( $type eq 'int8' ) { ( $value ) = unpack( 'c', $data ); } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { ( $value ) = unpack( 'C', $data ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { ( $value ) = unpack( 'Z'.$size, $data ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { ( $value ) = unpack( 'C'.$size, $data ); } else { Error( "Unexpected type '".$type."' found for '".$field."'" ); next; } push @values, $value; } if ( wantarray() ) { return @values; } return $values[0]; } sub zmMemInvalidate { my $monitor = shift; my $mem_key = zmMemKey($monitor); if ( $mem_key ) { zmMemDetach( $monitor ); } else { print "no memkey in zmMemInvalidate\n"; } } sub zmMemTidy { zmMemClean(); } sub zmMemWrite { my $monitor = shift; my $field_values = shift; my $nocheck = shift; if ( !($nocheck || zmMemVerify( $monitor )) ) { return( undef ); } while ( my ( $field, $value ) = each( %$field_values ) ) { my ( $section, $element ) = split( /[\/:.]/, $field ); if ( !$section || !$element ) { Fatal( "Invalid shared data selector '$field'" ); } my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; my $type = $mem_data->{$section}->{contents}->{$element}->{type}; my $size = $mem_data->{$section}->{contents}->{$element}->{size}; my $data; if ( $type eq 'long' ) { $data = pack( 'l!', $value ); } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { $data = pack( 'L!', $value ); } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { # The 'q' type is only available on 64bit platforms, so use native. $data = pack( 'l!', $value ); } elsif ( $type eq 'uint64' ) { # The 'q' type is only available on 64bit platforms, so use native. $data = pack( 'L!', $value ); } elsif ( $type eq 'int32' ) { $data = pack( 'l', $value ); } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { $data = pack( 'L', $value ); } elsif ( $type eq 'int16' ) { $data = pack( 's', $value ); } elsif ( $type eq 'uint16' ) { $data = pack( 'S', $value ); } elsif ( $type eq 'int8' ) { $data = pack( 'c', $value ); } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { $data = pack( 'C', $value ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { $data = pack( 'Z'.$size, $value ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { $data = pack( 'C'.$size, $value ); } else { Fatal( "Unexpected type \"$type\" found for \"$field\"" ); } if ( !zmMemPut( $monitor, $offset, $size, $data ) ) { Error( "Unable to write '$value' to '$field' in memory for monitor " .$monitor->{Id} ); zmMemInvalidate( $monitor ); return( undef ); } } return( !undef ); } sub zmGetMonitorState { my $monitor = shift; return( zmMemRead( $monitor, 'shared_data:state' ) ); } sub zmGetAlarmLocation { my $monitor = shift; return( zmMemRead( $monitor, [ 'shared_data:alarm_x', 'shared_data:alarm_y' ] ) ); } sub zmSetControlState { my $monitor = shift; my $control_state = shift; zmMemWrite( $monitor, { 'shared_data:control_state' => $control_state } ); } sub zmGetControlState { my $monitor = shift; return( zmMemRead( $monitor, 'shared_data:control_state' ) ); } sub zmSaveControlState { my $monitor = shift; my $control_state = shift; zmSetControlState( $monitor, freeze( $control_state ) ); } sub zmRestoreControlState { my $monitor = shift; return( thaw( zmGetControlState( $monitor ) ) ); } sub zmIsAlarmed { my $monitor = shift; my $state = zmGetMonitorState( $monitor ); return( $state == STATE_ALARM ); } sub zmInAlarm { my $monitor = shift; my $state = zmGetMonitorState( $monitor ); return( $state == STATE_ALARM || $state == STATE_ALERT ); } sub zmHasAlarmed { my $monitor = shift; my $last_event_id = shift; my ( $state, $last_event ) = zmMemRead( $monitor, [ 'shared_data:state' ,'shared_data:last_event' ] ); if ( $state == STATE_ALARM || $state == STATE_ALERT ) { return( $last_event ); } elsif( $last_event != $last_event_id ) { return( $last_event ); } return( undef ); } sub zmGetStartupTime { return zmMemRead( $_[0], 'shared_data:startup_time' ); } sub zmGetLastEvent { my $monitor = shift; return( zmMemRead( $monitor, 'shared_data:last_event' ) ); } sub zmGetLastWriteTime { my $monitor = shift; return( zmMemRead( $monitor, 'shared_data:last_write_time' ) ); } sub zmGetLastReadTime { my $monitor = shift; return( zmMemRead( $monitor, 'shared_data:last_read_time' ) ); } sub zmGetMonitorActions { my $monitor = shift; return( zmMemRead( $monitor, 'shared_data:action' ) ); } sub zmMonitorEnable { my $monitor = shift; my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorDisable { my $monitor = shift; my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorSuspend { my $monitor = shift; my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorResume { my $monitor = shift; my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmGetTriggerState { my $monitor = shift; return( zmMemRead( $monitor, 'trigger_data:trigger_state' ) ); } sub zmTriggerEventOn { my $monitor = shift; my $score = shift; my $cause = shift; my $text = shift; my $showtext = shift; my $values = { 'trigger_data:trigger_score' => $score, 'trigger_data:trigger_cause' => $cause, }; $values->{'trigger_data:trigger_text'} = $text if ( defined($text) ); $values->{'trigger_data:trigger_showtext'} = $showtext if ( defined($showtext) ); $values->{'trigger_data:trigger_state'} = TRIGGER_ON; # Write state last so event not read incomplete zmMemWrite( $monitor, $values ); } sub zmTriggerEventOff { my $monitor = shift; my $values = { 'trigger_data:trigger_state' => TRIGGER_OFF, 'trigger_data:trigger_score' => 0, 'trigger_data:trigger_cause' => '', 'trigger_data:trigger_text' => '', 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); } sub zmTriggerEventCancel { my $monitor = shift; my $values = { 'trigger_data:trigger_state' => TRIGGER_CANCEL, 'trigger_data:trigger_score' => 0, 'trigger_data:trigger_cause' => '', 'trigger_data:trigger_text' => '', 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); } sub zmTriggerShowtext { my $monitor = shift; my $showtext = shift; my $values = { 'trigger_data:trigger_showtext' => $showtext, }; zmMemWrite( $monitor, $values ); } 1; __END__ =head1 NAME ZoneMinder::MappedMem - ZoneMinder Mapped Memory access module =head1 SYNOPSIS use ZoneMinder::MappedMem; use ZoneMinder::MappedMem qw(:all); if ( zmMemVerify( $monitor ) ) { $state = zmGetMonitorState( $monitor ); if ( $state == STATE_ALARM ) { ... } } ( $lri, $lwi ) = zmMemRead( $monitor, [ 'shared_data:last_read_index', 'shared_data:last_write_index' ] ); zmMemWrite( $monitor, { 'trigger_data:trigger_showtext' => "Some Text" } ); =head1 DESCRIPTION The ZoneMinder:MappedMem module contains methods for accessing and writing to mapped memory as well as helper methods for common operations. The core elements of ZoneMinder used mapped memory to allow multiple access to resources. Although ZoneMinder scripts have used this information before, up until now it was difficult to access and prone to errors. This module introduces a common API for mapped memory access (both reading and writing) making it a lot easier to customise scripts or even create your own. All the methods listed below require a 'monitor' parameter. This must be a reference to a hash with at least the 'Id' field set to the monitor id of the mapped memory you wish to access. Using database methods to select the monitor details will also return this kind of data. Some of the mapped memory methods will add and amend new fields to this hash. =head1 METHODS =over 4 =item zmMemVerify ( $monitor ); Verify that the mapped memory of the monitor given exists and is valid. It will return an undefined value if it is not valid. You should generally call this method first before using any of the other methods, but most of the remaining methods will also do so if the memory has not already been verified. =item zmMemInvalidate ( $monitor ); Following an error, reset the mapped memory ids and attempt to reverify on the next operation. This is mostly used when a mapped memory segment has gone away and been recreated with a different id. =item zmMemRead ( $monitor, $readspec ); This method is used to read data from mapped memory attached to the given monitor. The mapped memory will be verified if it has not already been. The 'readspec' must either be a string of the form "
:" or a reference to an array of strings of the same format. In the first case a single value is returned, in the latter case a list of values is return. Errors will cause undefined to be returned. The allowable sections and field names are described below. =item zmMemWrite ( $monitor, $writespec ); This method is used to write data to mapped memory attached to the given monitor. The mapped memory will be verified if it has not already been. The 'writespec' must be a reference to a hash with keys of the form "
:" and values as the data to be written. Errors will cause undefined to be returned, otherwise a non-undefined value will be returned. The allowable sections and field names are described below. =item $state = zmGetMonitorState ( $monitor ); Return the current state of the given monitor. This is an integer value and can be compared with the STATE constants given below. =item $event_id = zmGetLastEvent ( $monitor ); Return the event id of the last event that the monitor generated, or 0 if no event has been generated by the current monitor process. =item zmIsAlarmed ( $monitor ); Return 1 if the monitor given is currently in an alarm state, 0 otherwise. =item zmInAlarm ( $monitor ); Return 1 if the monitor given is currently in an alarm or alerted state, 0 otherwise. =item zmHasAlarmed ( $monitor ); Return 1 if the given monitor is in an alarm state, or has been in an alarm state since the last call to this method. =item ( $x, $y ) = zmGetAlarmLocation ( $monitor ); Return an x,y pair indicating the image co-ordinates of the centre of the last motion event generated by the given monitor. If no event has been generated by the current monitor process, or the alarm was not motion related, returns -1,-1. =item zmGetLastWriteTime ( $monitor ); Returns the time (in utc seconds) since the last image was captured by the given monitor and written to shared memory, or 0 otherwise. =item zmGetLastReadTime ( $monitor ); Returns the time (in utc seconds) since the last image was read from shared memory by the analysis daemon of the given monitor, or 0 otherwise or if the monitor is in monitor only mode. =item zmMonitorSuspend ( $monitor ); Suspend the given monitor from generating events caused by motion. This method can be used to prevent camera actions such as panning or zooming from causing events. If configured to do so, the monitor may automatically resume after a defined period. =item zmMonitorResume ( $monitor ); Allow the given monitor to resume generating events caused by motion. =item zmTriggerEventOn ( $monitor, $score, $cause [, $text, $showtext ] ); Trigger the given monitor to generate an event. You must supply an event score and a cause string indicating the reason for the event. You may also supply a text string containing further details about the event and a showtext string which may be included in the timestamp annotation on any images captured during the event, if configured to do so. =item zmTriggerEventOff ( $monitor ); Trigger the given monitor to not generate any events. This method does not cancel zmTriggerEventOn, but is exclusive to it. This method is intended to allow external triggers to prevent normal events being generated by monitors in the same way as zmMonitorSuspend but applies to all events and not just motion, and is intended for longer timescales than are appropriate for suspension. =item zmTriggerEventCancel ( $monitor ); Cancel any previous trigger on or off requests. This stops a triggered alarm if it exists from a previous 'on' and allows events to be generated once more following a previous 'off'. =item zmTriggerShowtext ( $monitor, $showtest ); Indicate that the given text should be displayed in the timestamp annotation on any images captured, if the format of the annotation string defined for the monitor permits. =back =head1 DATA The data fields in mapped memory that may be accessed are as follows. There are two main sections, shared_data which is general data and trigger_data which is used for event triggering. Whilst reading from these fields is harmless, extreme care must be taken when writing to mapped memory, especially in the shared_data section as this is normally written to only by monitor capture and analysis processes. shared_data The general mapped memory section size The size, in bytes, of this section valid Flag indicating whether this section has been initialised active Flag indicating whether this monitor is active (enabled/disabled) signal Flag indicating whether this monitor is reciving a valid signal state The current monitor state, see the STATE constants below last_write_index The last index, in the image buffer, that an image has been saved to last_read_index The last index, in the image buffer, that an image has been analysed from last_write_time The time (in utc seconds) when the last image was captured last_read_time The time (in utc seconds) when the last image was analysed last_event The id of the last event generated by the monitor analysis process, 0 if none action The monitor actions bitmask, see the ACTION constants below brightness Read/write location for the current monitor brightness hue Read/write location for the current monitor hue colour Read/write location for the current monitor colour contrast Read/write location for the current monitor contrast alarm_x Image x co-ordinate (from left) of the centre of the last motion event, -1 if none alarm_y Image y co-ordinate (from top) of the centre of the last motion event, -1 if none alarm_cause The current alarm event cause string along with zone names(s) alarmed trigger_data The triggered event mapped memory section size The size, in bytes of this section trigger_state The current trigger state, see the TRIGGER constants below trigger_score The current triggered event score trigger_cause The current triggered event cause string trigger_text The current triggered event descriptive text string trigger_showtext The triggered text that will be displayed on captured image timestamps =head1 CONSTANTS The following constants are used by the methods above, but can also be used by user scripts if required. =over 4 =item STATE_IDLE STATE_PREALARM STATE_ALARM STATE_ALERT STATE_TAPE These constants define the state of the monitor with respect to alarms and events. They are used in the shared_data:state field. =item ACTION_GET ACTION_SET ACTION_RELOAD ACTION_SUSPEND ACTION_RESUME These constants defines the various values that can exist in the shared_data:action field. This is a bitmask which when non-zero defines an action that an executing monitor process should take. ACTION_GET requires that the current values of brightness, contrast, colour and hue are taken from the camera and written to the equivalent mapped memory fields. ACTION_SET implies the reverse, that the values in mapped memory should be written to the camera. ACTION_RELOAD signal that the monitor process should reload itself from the database in case any settings have changed there. ACTION_SUSPEND signals that a monitor should stop exaiming images for motion, though other alarms may still occur. ACTION_RESUME sigansl that a monitor should resume motion detectiom. =item TRIGGER_CANCEL TRIGGER_ON TRIGGER_OFF These constants are used in the definition of external triggers. TRIGGER_CANCEL is used to indicated that any previous trigger settings should be cancelled, TRIGGER_ON signals that an alarm should be created (or continued)) as a result of the current trigger and TRIGGER_OFF signals that the trigger should prevent any alarms from being generated. See the trigger methods above for further details. =back =head1 EXPORT None by default. The :constants tag will export the mapped memory constants which mostly define enumerations for the variables held in memory The :functions tag will export the mapped memory access functions. The :all tag will export all above symbols. =head1 SEE ALSO http://www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm0000644000000000000000000001125613365153155022706 0ustar rootroot# ========================================================================== # # ZoneMinder Storage Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Storage; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Object; require ZoneMinder::Server; use parent qw(Exporter ZoneMinder::Object); # ========================================================================== # # General Utility Functions # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); use POSIX; use vars qw/ $table $primary_key /; $table = 'Storage'; $primary_key = 'Id'; #__PACKAGE__->table('Storage'); #__PACKAGE__->primary_key('Id'); sub find { shift if $_[0] eq 'ZoneMinder::Storage'; my %sql_filters = @_; my $sql = 'SELECT * FROM Storage'; my @sql_filters; my @sql_values; if ( exists $sql_filters{Id} ) { push @sql_filters , ' Id=? '; push @sql_values, $sql_filters{Id}; } if ( exists $sql_filters{Name} ) { push @sql_filters , ' Name = ? '; push @sql_values, $sql_filters{Name}; } if ( exists $sql_filters{ServerId} ) { push @sql_filters, ' ServerId = ?'; push @sql_values, $sql_filters{ServerId}; } $sql .= ' WHERE ' . join(' AND ', @sql_filters) if @sql_filters; $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); my $res = $sth->execute( @sql_values ) or Fatal( "Can't execute '$sql': ".$sth->errstr() ); my @results; while( my $db_filter = $sth->fetchrow_hashref() ) { my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while Debug("SQL: $sql returned " . @results . ' results'); return @results; } sub find_one { my @results = find(@_); return $results[0] if @results; } sub Path { if ( @_ > 1 ) { $_[0]{Path} = $_[1]; } if ( ! ( $_[0]{Id} or $_[0]{Path} ) ) { $_[0]{Path} = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) } return $_[0]{Path}; } # end sub Path sub Name { if ( @_ > 1 ) { $_[0]{Name} = $_[1]; } return $_[0]{Name}; } # end sub Path sub DoDelete { my $self = shift; $$self{DoDelete} = shift if @_; if ( ! defined $$self{DoDelete} ) { $$self{DoDelete} = 1; } return $$self{DoDelete}; } sub Server { my $self = shift; if ( ! $$self{Server} ) { $$self{Server} = new ZoneMinder::Server( $$self{ServerId} ); } return $$self{Server}; } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Storage; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/General.pm0000644000000000000000000004136613365153155022664 0ustar rootroot# ========================================================================== # # ZoneMinder General Utility Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::General; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; require ZoneMinder::Storage; our @ISA = qw(Exporter ZoneMinder::Base); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( executeShellCommand getCmdFormat runCommand setFileOwner createEventPath createEvent makePath jsonEncode jsonDecode ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # General Utility Functions # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); use POSIX; # For running general shell commands sub executeShellCommand { my $command = shift; my $output = qx( $command ); my $status = $? >> 8; if ( $status || logDebugging() ) { Debug( "Command: $command\n" ); chomp( $output ); Debug( "Output: $output\n" ); } return( $status ); } sub getCmdFormat { Debug( "Testing valid shell syntax\n" ); my ( $name ) = getpwuid( $> ); if ( $name eq $Config{ZM_WEB_USER} ) { Debug( "Running as '$name', su commands not needed\n" ); return( "" ); } my $null_command = "true"; my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." "; my $suffix = ""; my $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); my $output = qx($command 2>&1); my $status = $? >> 8; $output //= $!; if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); } else { chomp( $output ); Debug( "Test failed, '$output'\n" ); $prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='"; $suffix = "'"; $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); my $output = qx($command 2>&1); my $status = $? >> 8; $output //= $!; if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); } else { chomp( $output ); Debug( "Test failed, '$output'\n" ); $prefix = "su ".$Config{ZM_WEB_USER}." -c '"; $suffix = "'"; $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); $output = qx($command 2>&1); $status = $? >> 8; $output //= $!; if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); } else { chomp( $output ); Debug( "Test failed, '$output'\n" ); } } } Error( "Unable to find valid 'su' syntax\n" ); exit( -1 ); } our $testedShellSyntax = 0; our ( $cmdPrefix, $cmdSuffix ); # For running ZM daemons etc sub runCommand { if ( !$testedShellSyntax ) { # Determine the appropriate syntax for the su command ( $cmdPrefix, $cmdSuffix ) = getCmdFormat(); $testedShellSyntax = !undef; } my $command = shift; $command = $Config{ZM_PATH_BIN}."/".$command; if ( $cmdPrefix ) { $command = $cmdPrefix.$command.$cmdSuffix; } Debug( "Command: $command\n" ); my $output = qx($command); my $status = $? >> 8; chomp( $output ); if ( $status || logDebugging() ) { if ( $status ) { Error( "Unable to run \"$command\", output is \"$output\", status is $status\n" ); } else { Debug( "Output: $output\n" ); } } return( $output ); } sub createEventPath { my $event = shift; my $eventPath = $event->Path(); $event->createPath(); $event->createIdFile(); $event->createLinkPath(); return $eventPath; } use Data::Dumper; our $_setFileOwner = undef; our ( $_ownerUid, $_ownerGid ); sub _checkProcessOwner { if ( !defined($_setFileOwner) ) { my ( $processOwner ) = getpwuid( $> ); if ( $processOwner ne $Config{ZM_WEB_USER} ) { # Not running as web user, so should be root in which case chown # the temporary directory ( my $ownerName, my $ownerPass, $_ownerUid, $_ownerGid ) = getpwnam( $Config{ZM_WEB_USER} ) or Fatal( "Can't get user details for web user '" .$Config{ZM_WEB_USER}."': $!" ); $_setFileOwner = 1; } else { $_setFileOwner = 0; } } return( $_setFileOwner ); } sub setFileOwner { my $file = shift; if ( _checkProcessOwner() ) { chown( $_ownerUid, $_ownerGid, $file ) or Fatal( "Can't change ownership of file '$file' to '" .$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" ); } } our $_hasImageInfo = undef; sub _checkForImageInfo { if ( !defined($_hasImageInfo) ) { my $result = eval { require Image::Info; Image::Info->import(); }; $_hasImageInfo = $@?0:1; } return( $_hasImageInfo ); } sub createEvent { my $event = shift; Debug( "Creating event" ); #print( Dumper( $event )."\n" ); _checkForImageInfo(); my $dbh = zmDbConnect(); if ( $event->{monitor} ) { $event->{MonitorId} = $event->{monitor}->{Id}; } elsif ( $event->{MonitorId} ) { my $sql = "select * from Monitors where Id = ?"; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $event->{MonitorId} ) or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); $event->{monitor} = $sth->fetchrow_hashref() or Fatal( "Unable to create event, can't load monitor with id '" .$event->{MonitorId}."'" ); $sth->finish(); } else { Fatal( "Unable to create event, no monitor or monitor id supplied" ); } $event->{Name} = "New Event" unless( $event->{Name} ); $event->{Frames} = int(@{$event->{frames}}); $event->{TotScore} = $event->{MaxScore} = 0; my $lastTimestamp = 0.0; foreach my $frame ( @{$event->{frames}} ) { if ( !$event->{Width} ) { if ( $_hasImageInfo ) { my $imageInfo = Image::Info::image_info( $frame->{imagePath} ); if ( $imageInfo->{error} ) { Error( "Unable to extract image info from '" .$frame->{imagePath}."': ".$imageInfo->{error} ); } else { ( $event->{Width}, $event->{Height} ) = Image::Info::dim( $imageInfo ); } } } $frame->{Type} = $frame->{Score}>0?'Alarm':'Normal' unless( $frame->{Type} ); $frame->{Delta} = $lastTimestamp?($frame->{TimeStamp}-$lastTimestamp):0.0; $event->{StartTime} = $frame->{TimeStamp} unless ( $event->{StartTime} ); $event->{TotScore} += $frame->{Score}; $event->{MaxScore} = $frame->{Score} if ( $frame->{Score} > $event->{MaxScore} ); $event->{AlarmFrames}++ if ( $frame->{Type} eq 'Alarm' ); $event->{EndTime} = $frame->{TimeStamp}; $lastTimestamp = $frame->{TimeStamp}; } $event->{Width} = $event->{monitor}->{Width} unless( $event->{Width} ); $event->{Height} = $event->{monitor}->{Height} unless( $event->{Height} ); $event->{AvgScore} = $event->{TotScore}/int($event->{AlarmFrames}); $event->{Length} = $event->{EndTime} - $event->{StartTime}; my %formats = ( StartTime => 'from_unixtime(?)', EndTime => 'from_unixtime(?)', ); my ( @fields, @formats, @values ); while ( my ( $field, $value ) = each( %$event ) ) { next unless $field =~ /^[A-Z]/; push( @fields, $field ); push( @formats, ($formats{$field} or '?') ); push( @values, $event->{$field} ); } my $sql = "INSERT INTO Events (".join(',',@fields) .") VALUES (".join(',',@formats).")" ; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); my $res = $sth->execute( @values ) or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); $event->{Id} = $dbh->{mysql_insertid}; Info( "Created event ".$event->{Id} ); if ( $event->{EndTime} ) { $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} if ( $event->{Name} eq 'New Event' ); my $sql = "update Events set Name = ? where Id = ?"; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $event->{Name}, $event->{Id} ) or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); } my $eventPath = createEventPath( $event ); my %frameFormats = ( TimeStamp => 'from_unixtime(?)', ); my $frameId = 1; foreach my $frame ( @{$event->{frames}} ) { $frame->{EventId} = $event->{Id}; $frame->{FrameId} = $frameId++; my ( @fields, @formats, @values ); while ( my ( $field, $value ) = each( %$frame ) ) { next unless $field =~ /^[A-Z]/; push( @fields, $field ); push( @formats, ($frameFormats{$field} or '?') ); push( @values, $frame->{$field} ); } my $sql = "insert into Frames (".join(',',@fields) .") values (".join(',',@formats).")" ; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); my $res = $sth->execute( @values ) or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); #$frame->{FrameId} = $dbh->{mysql_insertid}; if ( $frame->{imagePath} ) { $frame->{capturePath} = sprintf( "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} ."d-capture.jpg" , $eventPath , $frame->{FrameId} ); rename( $frame->{imagePath}, $frame->{capturePath} ) or Fatal( "Can't copy ".$frame->{imagePath} ." to ".$frame->{capturePath}.": $!" ); setFileOwner( $frame->{capturePath} ); if ( 0 && $Config{ZM_CREATE_ANALYSIS_IMAGES} ) { $frame->{analysePath} = sprintf( "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} ."d-analyse.jpg" , $eventPath , $frame->{FrameId} ); link( $frame->{capturePath}, $frame->{analysePath} ) or Fatal( "Can't link ".$frame->{capturePath} ." to ".$frame->{analysePath}.": $!" ); setFileOwner( $frame->{analysePath} ); } } } } sub addEventImage { my $event = shift; my $frame = shift; # TBD } sub updateEvent { my $event = shift; if ( !$event->{EventId} ) { Error( "Unable to update event, no event id supplied" ); return( 0 ); } my $dbh = zmDbConnect(); $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} if ( $event->{Name} eq 'New Event' ); my %formats = ( StartTime => 'from_unixtime(?)', EndTime => 'from_unixtime(?)', ); my ( @values, @sets ); while ( my ( $field, $value ) = each( %$event ) ) { next if ( $field eq 'Id' ); push( @values, $event->{$field} ); push( @sets, $field." = ".($formats{$field} or '?') ); } my $sql = "update Events set ".join(',',@sets)." where Id = ?"; push( @values, $event->{Id} ); my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); my $res = $sth->execute( @values ) or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); } sub makePath { my $path = shift; my $root = shift; $root = (( $path =~ m|^/| )?'':'.' ) unless( $root ); Debug( "Creating path '$path' in $root'\n" ); my @parts = split( '/', $path ); my $fullPath = $root; foreach my $dir ( @parts ) { $fullPath .= '/'.$dir; if ( !-d $fullPath ) { if ( -e $fullPath ) { Fatal( "Can't create '$fullPath', already exists as non directory" ); } else { Debug( "Creating '$fullPath'\n" ); mkdir( $fullPath, 0755 ) or Fatal( "Can't mkdir '$fullPath': $!" ); setFileOwner( $fullPath ); } } } return( $fullPath ); } our $testedJSON = 0; our $hasJSONAny = 0; sub _testJSON { return if ( $testedJSON ); my $result = eval { require JSON::MaybeXS; JSON::MaybeXS->import(); }; $testedJSON = 1; $hasJSONAny = 1 if ( $result ); } sub _getJSONType { my $value = shift; return( 'null' ) unless( defined($value) ); return( 'integer' ) if ( $value =~ /^\d+$/ ); return( 'double' ) if ( $value =~ /^\d+$/ ); return( 'hash' ) if ( ref($value) eq 'HASH' ); return( 'array' ) if ( ref($value) eq 'ARRAY' ); return( 'string' ); } sub jsonEncode; sub jsonEncode { my $value = shift; _testJSON(); if ( $hasJSONAny ) { my $string = eval { JSON::MaybeXS->encode_json( $value ) }; Fatal( "Unable to encode object to JSON: $@" ) unless( $string ); return( $string ); } my $type = _getJSONType($value); if ( $type eq 'integer' || $type eq 'double' ) { return( $value ); } elsif ( $type eq 'boolean' ) { return( $value?'true':'false' ); } elsif ( $type eq 'string' ) { $value =~ s|(["\\/])|\\$1|g; $value =~ s|\r?\n|\n|g; return( '"'.$value.'"' ); } elsif ( $type eq 'null' ) { return( 'null' ); } elsif ( $type eq 'array' ) { return( '['.join( ',', map { jsonEncode( $_ ) } @$value ).']' ); } elsif ( $type eq 'hash' ) { my $result = '{'; while ( my ( $subKey=>$subValue ) = each( %$value ) ) { $result .= ',' if ( $result ne '{' ); $result .= '"'.$subKey.'":'.jsonEncode( $subValue ); } return( $result.'}' ); } else { Fatal( "Unexpected type '$type'" ); } } sub jsonDecode { my $value = shift; _testJSON(); if ( $hasJSONAny ) { my $object = eval { JSON::MaybeXS->decode_json( $value ) }; Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object ); return( $object ); } my $comment = 0; my $unescape = 0; my $out = ''; my @chars = split( //, $value ); for ( my $i = 0; $i < @chars; $i++ ) { if ( !$comment ) { if ( $chars[$i] eq ':' ) { $out .= '=>'; } else { $out .= $chars[$i]; } } elsif ( !$unescape ) { if ( $chars[$i] eq '\\' ) { $unescape = 1; } else { $out .= $chars[$i]; } } else { if ( $chars[$i] ne '/' ) { $out .= '\\'; } $out .= $chars[$i]; $unescape = 0; } if ( $chars[$i] eq '"' ) { $comment = !$comment; } } $out =~ s/=>true/=>1/g; $out =~ s/=>false/=>0/g; $out =~ s/=>null/=>undef/g; $out =~ s/`/'/g; $out =~ s/qx/qq/g; ( $out ) = $out =~ m/^(\{.+\})$/; # Detaint and check it's a valid object syntax my $result = eval $out; Fatal( $@ ) if ( $@ ); return( $result ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm0000644000000000000000000005746613365153155022536 0ustar rootroot# ========================================================================== # # ZoneMinder Logger Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the debug definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Logger; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter ZoneMinder::Base); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( constants => [ qw( DEBUG INFO WARNING ERROR FATAL PANIC NOLOG ) ], functions => [ qw( logInit logReinit logTerm logSetSignal logClearSignal logDebugging logLevel logTermLevel logDatabaseLevel logFileLevel logSyslogLevel Mark Dump Debug Info Warning Error Fatal Panic ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Logger Facilities # # ========================================================================== use ZoneMinder::Config qw(:all); use DBI; use Carp; use POSIX; use IO::Handle; use Data::Dumper; use Time::HiRes qw/gettimeofday/; use Sys::Syslog; use constant { DEBUG => 1, INFO => 0, WARNING => -1, ERROR => -2, FATAL => -3, PANIC => -4, NOLOG => -5 }; our %codes = ( &DEBUG => 'DBG', &INFO => 'INF', &WARNING => 'WAR', &ERROR => 'ERR', &FATAL => 'FAT', &PANIC => 'PNC', &NOLOG => 'OFF' ); our %priorities = ( &DEBUG => 'debug', &INFO => 'info', &WARNING => 'warning', &ERROR => 'err', &FATAL => 'err', &PANIC => 'err' ); our $logger; our $LOGFILE; sub new { my $class = shift; my $this = {}; $this->{initialised} = undef; ( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|; $this->{idRoot} = $this->{id}; $this->{idArgs} = ''; $this->{level} = INFO; # Detect if we are running in a terminal session, if so, default log level to INFO $this->{hasTerm} = -t STDERR; if ( $this->{hasTerm} ) { $this->{termLevel} = INFO; } else { $this->{termLevel} = NOLOG; } $this->{databaseLevel} = NOLOG; $this->{fileLevel} = NOLOG; $this->{syslogLevel} = NOLOG; $this->{effectiveLevel} = INFO; $this->{autoFlush} = 1; ( $this->{fileName} = $0 ) =~ s|^.*/||; $this->{logPath} = $Config{ZM_PATH_LOGS}; $this->{logFile} = $this->{logPath}.'/'.$this->{id}.'.log'; ($this->{logFile}) = $this->{logFile} =~ /^([\w\.\/]+)$/; $this->{trace} = 0; bless( $this, $class ); return $this; } sub BEGIN { # Fake the config variables that are used in case they are not defined yet # Only really necessary to support upgrade from previous version if ( !eval('defined($Config{ZM_LOG_DEBUG})') ) { no strict 'subs'; no strict 'refs'; my %dbgConfig = ( ZM_LOG_LEVEL_DATABASE => 0, ZM_LOG_LEVEL_FILE => 0, ZM_LOG_LEVEL_SYSLOG => 0, ZM_LOG_DEBUG => 0, ZM_LOG_DEBUG_TARGET => '', ZM_LOG_DEBUG_LEVEL => 1, ZM_LOG_DEBUG_FILE => '' ); while ( my ( $name, $value ) = each( %dbgConfig ) ) { *{$name} = sub { $value }; } use strict 'subs'; use strict 'refs'; } } sub DESTROY { my $this = shift; $this->terminate(); } sub initialise( @ ) { my $this = shift; my %options = @_; $this->{hasTerm} = -t STDERR; $this->{id} = $options{id} if defined($options{id}); $this->{logPath} = $options{logPath} if defined($options{logPath}); my $tempLogFile; $tempLogFile = $this->{logPath}.'/'.$this->{id}.'.log'; $tempLogFile = $options{logFile} if defined($options{logFile}); if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) { $tempLogFile = $logFile; } ($tempLogFile) = $tempLogFile =~ /^([\w\.\/]+)$/; my $tempLevel = INFO; my $tempTermLevel = $this->{termLevel}; my $tempDatabaseLevel = $this->{databaseLevel}; my $tempFileLevel = $this->{fileLevel}; my $tempSyslogLevel = $this->{syslogLevel}; $tempTermLevel = $options{termLevel} if defined($options{termLevel}); if ( defined($options{databaseLevel}) ) { $tempDatabaseLevel = $options{databaseLevel}; } else { $tempDatabaseLevel = $Config{ZM_LOG_LEVEL_DATABASE}; } if ( defined($options{fileLevel}) ) { $tempFileLevel = $options{fileLevel}; } else { $tempFileLevel = $Config{ZM_LOG_LEVEL_FILE}; } if ( defined($options{syslogLevel}) ) { $tempSyslogLevel = $options{syslogLevel}; } else { $tempSyslogLevel = $Config{ZM_LOG_LEVEL_SYSLOG}; } if ( defined($ENV{LOG_PRINT}) ) { $tempTermLevel = $ENV{LOG_PRINT}? DEBUG : NOLOG; } my $level; $tempLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL')); $tempTermLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')); $tempDatabaseLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')); $tempFileLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')); $tempSyslogLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')); if ( $Config{ZM_LOG_DEBUG} ) { # Splitting on an empty string doesn't return an empty string, it returns an empty array foreach my $target ( $Config{ZM_LOG_DEBUG_TARGET} ? split(/\|/, $Config{ZM_LOG_DEBUG_TARGET}) : '' ) { if ( $target eq $this->{id} || $target eq '_'.$this->{id} || $target eq $this->{idRoot} || $target eq '_'.$this->{idRoot} || $target eq '' ) { if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) { $tempLevel = $this->limit( $Config{ZM_LOG_DEBUG_LEVEL} ); if ( $Config{ZM_LOG_DEBUG_FILE} ne '' ) { $tempLogFile = $Config{ZM_LOG_DEBUG_FILE}; $tempFileLevel = $tempLevel; } } } } } $this->logFile( $tempLogFile ); $this->termLevel( $tempTermLevel ); $this->databaseLevel( $tempDatabaseLevel ); $this->fileLevel( $tempFileLevel ); $this->syslogLevel( $tempSyslogLevel ); $this->level( $tempLevel ); $this->{trace} = $options{trace} if defined($options{trace}); $this->{autoFlush} = $ENV{LOG_FLUSH}?1:0 if defined($ENV{LOG_FLUSH}); $this->{initialised} = !undef; # this function can get called on a previously initialized log Object, so clean any sth's $this->{sth} = undef; Debug( 'LogOpts: level='.$codes{$this->{level}} .'/'.$codes{$this->{effectiveLevel}} .', screen='.$codes{$this->{termLevel}} .', database='.$codes{$this->{databaseLevel}} .', logfile='.$codes{$this->{fileLevel}} .'->'.$this->{logFile} .', syslog='.$codes{$this->{syslogLevel}} ); } sub terminate { my $this = shift; return unless ( $this->{initialised} ); $this->syslogLevel( NOLOG ); $this->fileLevel( NOLOG ); $this->databaseLevel( NOLOG ); $this->termLevel( NOLOG ); } sub reinitialise { my $this = shift; # So if the logger is initialized, we just return. Since the logger is NORMALLY initialized... the rest of this function never executes. return unless ( $this->{initialised} ); # Bit of a nasty hack to reopen connections to log files and the DB my $syslogLevel = $this->syslogLevel(); $this->syslogLevel( NOLOG ); $this->syslogLevel($syslogLevel) if $syslogLevel > NOLOG; my $logfileLevel = $this->fileLevel(); $this->fileLevel(NOLOG); $this->fileLevel($logfileLevel) if $logfileLevel > NOLOG; my $databaseLevel = $this->databaseLevel(); $this->databaseLevel(NOLOG); $this->databaseLevel($databaseLevel) if $databaseLevel > NOLOG; my $screenLevel = $this->termLevel(); $this->termLevel(NOLOG); $this->termLevel($screenLevel) if $screenLevel > NOLOG; $this->{sth} = undef; } # Prevents undefined logging levels sub limit { my $this = shift; my $level = shift; return(DEBUG) if $level > DEBUG; return(NOLOG) if $level < NOLOG; return($level); } sub getTargettedEnv { my $this = shift; my $name = shift; my $envName = $name.'_'.$this->{id}; my $value; $value = $ENV{$envName} if defined($ENV{$envName}); if ( !defined($value) and ($this->{id} ne $this->{idRoot}) ) { $envName = $name.'_'.$this->{idRoot}; $value = $ENV{$envName} if defined($ENV{$envName}); } if ( !defined($value) ) { $value = $ENV{$name} if defined($ENV{$name}); } if ( defined($value) ) { ( $value ) = $value =~ m/(.*)/; } return $value; } sub fetch { if ( !$logger ) { $logger = ZoneMinder::Logger->new(); $logger->initialise( syslogLevel=>INFO, databaseLevel=>INFO ); } return $logger; } sub id { my $this = shift; my $id = shift; if ( defined($id) and ($this->{id} ne $id) ) { # Remove whitespace $id =~ s/\S//g; # Replace non-alphanum with underscore $id =~ s/[^a-zA-Z_]/_/g; if ( $this->{id} ne $id ) { $this->{id} = $this->{idRoot} = $id; if ( $id =~ /^([^_]+)_(.+)$/ ) { $this->{idRoot} = $1; $this->{idArgs} = $2; } } } return $this->{id}; } sub level { my $this = shift; my $level = shift; if ( defined($level) ) { $this->{level} = $this->limit($level); # effectiveLevel is the highest logging level used by any of the outputs. $this->{effectiveLevel} = NOLOG; $this->{effectiveLevel} = $this->{termLevel} if $this->{termLevel} > $this->{effectiveLevel}; $this->{effectiveLevel} = $this->{databaseLevel} if $this->{databaseLevel} > $this->{effectiveLevel}; $this->{effectiveLevel} = $this->{fileLevel} if $this->{fileLevel} > $this->{effectiveLevel}; $this->{effectiveLevel} = $this->{syslogLevel} if $this->{syslogLevel} > $this->{effectiveLevel}; # ICON: I am remarking this out because I don't see the point of having an effective level, if we are just going to set it to level. #$this->{effectiveLevel} = $this->{level} if ( $this->{level} > $this->{effectiveLevel} ); # ICON: The point is that LOG_DEBUG can be set either in db or in env var and will get passed in here. # So this will turn on debug, even if not output has Debug level turned on. I think it should be the other way around # ICON: Let's try this line instead. effectiveLevel is 1 DEBUG from above, but LOG_DEBUG is off, then $this->level will be 0, and # so effectiveLevel will become 0 $this->{effectiveLevel} = $this->{level} if ( $this->{level} < $this->{effectiveLevel} ); } return $this->{level}; } sub debugOn { my $this = shift; return $this->{effectiveLevel} >= DEBUG; } sub trace { my $this = shift; $this->{trace} = $_[0] if @_; return $this->{trace}; } sub termLevel { my $this = shift; my $termLevel = shift; if ( defined($termLevel) ) { # What is the point of this next lint if we are just going to overwrite it with the next line? I propose we move it down one line or remove it altogether $termLevel = NOLOG if !$this->{hasTerm}; $termLevel = $this->limit($termLevel); if ( $this->{termLevel} != $termLevel ) { $this->{termLevel} = $termLevel; } } return $this->{termLevel}; } sub databaseLevel { my $this = shift; my $databaseLevel = shift; if ( defined($databaseLevel) ) { $databaseLevel = $this->limit($databaseLevel); if ( $this->{databaseLevel} != $databaseLevel ) { if ( ( $databaseLevel > NOLOG ) and ( $this->{databaseLevel} <= NOLOG ) ) { if ( !$this->{dbh} ) { $this->{dbh} = ZoneMinder::Database::zmDbConnect(); } } elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) { undef($this->{dbh}); } $this->{databaseLevel} = $databaseLevel; } } return $this->{databaseLevel}; } sub fileLevel { my $this = shift; my $fileLevel = shift; if ( defined($fileLevel) ) { $fileLevel = $this->limit($fileLevel); # The filename might have changed, so always close and re-open $this->closeFile() if ( $this->{fileLevel} > NOLOG ); $this->{fileLevel} = $fileLevel; $this->openFile() if ( $this->{fileLevel} > NOLOG ); } return $this->{fileLevel}; } sub syslogLevel { my $this = shift; my $syslogLevel = shift; if ( defined($syslogLevel) ) { $syslogLevel = $this->limit($syslogLevel); if ( $this->{syslogLevel} != $syslogLevel ) { $this->closeSyslog() if ( $syslogLevel <= NOLOG && $this->{syslogLevel} > NOLOG ); $this->openSyslog() if ( $syslogLevel > NOLOG && $this->{syslogLevel} <= NOLOG ); $this->{syslogLevel} = $syslogLevel; } } return( $this->{syslogLevel} ); } sub openSyslog { my $this = shift; openlog( $this->{id}, 'pid', 'local1' ); } sub closeSyslog { my $this = shift; closelog(); } sub logFile { my $this = shift; my $logFile = shift; if ( $logFile =~ /^(.+)\+$/ ) { $this->{logFile} = $1.'.'.$$; } else { $this->{logFile} = $logFile; } } sub openFile { my $this = shift; if ( open($LOGFILE, '>>', $this->{logFile}) ) { $LOGFILE->autoflush() if $this->{autoFlush}; my $webUid = (getpwnam($Config{ZM_WEB_USER}))[2]; my $webGid = (getgrnam($Config{ZM_WEB_GROUP}))[2]; if ( $> == 0 ) { chown( $webUid, $webGid, $this->{logFile} ) or Fatal("Can't change permissions on log file $$this{logFile}: $!"); } } else { $this->fileLevel(NOLOG); $this->termLevel(INFO); Error("Can't open log file $$this{logFile}: $!"); } } sub closeFile { #my $this = shift; close($LOGFILE) if fileno($LOGFILE); } sub logPrint { my $this = shift; my $level = shift; my $string = shift; my ($caller, undef, $line) = @_ ? @_ : caller; if ( $level <= $this->{effectiveLevel} ) { $string =~ s/[\r\n]+$//g; if ( $level <= $this->{syslogLevel} ) { syslog($priorities{$level}, $codes{$level}.' [%s]', $string); } my ($seconds, $microseconds) = gettimeofday(); if ( $level <= $this->{fileLevel} or $level <= $this->{termLevel} ) { my $message = sprintf( '%s.%06d %s[%d].%s [%s:%d] [%s]' , strftime('%x %H:%M:%S', localtime($seconds)) , $microseconds , $this->{id} , $$ , $codes{$level} , $caller , $line , $string ); if ( $this->{trace} ) { $message = Carp::shortmess($message); } else { $message = $message."\n"; } print($LOGFILE $message) if $level <= $this->{fileLevel}; print(STDERR $message) if $level <= $this->{termLevel}; } if ( $level <= $this->{databaseLevel} ) { if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) { $this->{sth} = undef; # Turn this off because zDbConnect will do logging calls. my $oldlevel = $this->{databaseLevel}; $this->{databaseLevel} = NOLOG; if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) { #print(STDERR "Can't log to database: "); return; } $this->{databaseLevel} = $oldlevel; } my $sql = 'INSERT INTO Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, NULL )'; $this->{sth} = $this->{dbh}->prepare_cached($sql) if ! $this->{sth}; if ( !$this->{sth} ) { $this->{databaseLevel} = NOLOG; Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr()); return; } my $res = $this->{sth}->execute( $seconds+($microseconds/1000000.0), $this->{id}, ($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : undef), $$, $level, $codes{$level}, $string, $this->{fileName}, ); if ( !$res ) { $this->{databaseLevel} = NOLOG; Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr()); } } # end if doing db logging } # end if level < effectivelevel } sub logInit( ;@ ) { my %options = @_ ? @_ : (); $logger = ZoneMinder::Logger->new() if !$logger; $logger->initialise(%options); } sub logReinit { fetch()->reinitialise(); } sub logTerm { return unless $logger; $logger->terminate(); $logger = undef; } sub logHupHandler { my $savedErrno = $!; return unless $logger; fetch()->reinitialise(); logSetSignal(); $! = $savedErrno; } sub logSetSignal { $SIG{HUP} = \&logHupHandler; } sub logClearSignal { $SIG{HUP} = 'DEFAULT'; } sub logLevel { return fetch()->level(@_); } sub logDebugging { return fetch()->debugOn(); } sub logTermLevel { return fetch()->termLevel(@_); } sub logDatabaseLevel { return fetch()->databaseLevel(@_); } sub logFileLevel { return fetch()->fileLevel(@_); } sub logSyslogLevel { return fetch()->syslogLevel(@_); } sub Mark { my $level = shift; $level = DEBUG unless defined($level); my $tag = 'Mark'; fetch()->logPrint($level, $tag); } sub Dump { my $var = shift; my $label = shift; $label = 'VAR' unless defined($label); fetch()->logPrint(DEBUG, Data::Dumper->Dump([ $var ], [ $label ])); } sub debug { my $log = shift; $log->logPrint(DEBUG, @_, caller); } sub Debug( @ ) { fetch()->logPrint(DEBUG, @_, caller); } sub Info( @ ) { fetch()->logPrint(INFO, @_, caller); } sub info { my $log = shift; $log->logPrint(INFO, @_, caller); } sub Warning( @ ) { fetch()->logPrint(WARNING, @_, caller); } sub warn { my $log = shift; $log->logPrint(WARNING, @_, caller); } sub Error( @ ) { fetch()->logPrint(ERROR, @_, caller); } sub error { my $log = shift; $log->logPrint(ERROR, @_, caller); } sub Fatal( @ ) { fetch()->logPrint(FATAL, @_, caller); if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) { $SIG{TERM}(); } # I think if we don't disconnect we will leave sockets around in TIME_WAIT zmDbDisconnect(); exit(-1); } sub Panic( @ ) { fetch()->logPrint(PANIC, @_, caller); confess($_[0]); } 1; __END__ =head1 NAME ZoneMinder::Logger - ZoneMinder Logger module =head1 SYNOPSIS use ZoneMinder::Logger; use ZoneMinder::Logger qw(:all); logInit( 'myproc', DEBUG ); Debug( 'This is what is happening' ); Info( 'Something interesting is happening' ); Warning( 'Something might be going wrong.' ); Error( 'Something has gone wrong!!' ); Fatal( 'Something has gone badly wrong, gotta stop!!' ); Panic( 'Something fundamental has gone wrong, die with stack trace' ); =head1 DESCRIPTION The ZoneMinder:Logger module contains the common debug and error reporting routines used by the ZoneMinder scripts. To use debug in your scripts you need to include this module, and call logInit. Thereafter you can sprinkle Debug or Error calls etc throughout the code safe in the knowledge that they will be reported to your error log, and possibly the syslogger, in a meaningful and consistent format. Debug is discussed in terms of levels where 1 and above (currently only 1 for scripts) is considered debug, 0 is considered as informational, -1 is a warning, -2 is an error and -3 is a fatal error or panic. Where levels are mentioned below as thresholds the value given and anything with a lower level (ie. more serious) will be included. =head1 METHODS =over 4 =item logInit ( $id, %options ); Initialises the debug and prepares the logging for forthcoming operations. If not called explicitly it will be called by the first debug call in your script, but with default (and probably meaningless) options. The only compulsory arguments are $id which must be a string that will identify debug coming from this script in mixed logs. Other options may be provided as below, Option Default Description --------- --------- ----------- level INFO The initial debug level which defines which statements are output and which are ignored trace 0 Whether to use the Carp::shortmess format in debug statements to identify where the debug was emitted from termLevel NOLOG At what level debug is written to terminal standard error, 0 is no, 1 is yes, 2 is write only if terminal databaseLevel INFO At what level debug is written to the Log table in the database; fileLevel NOLOG At what level debug is written to a log file of the format of .log in the standard log directory. syslogLevel INFO At what level debug is written to syslog. To disable any of these action entirely set to NOLOG =item logTerm (); Used to end the debug session and close any logs etc. Not usually necessary. =item $id = logId ( [$id] ); =item $level = logLevel ( [$level] ); =item $trace = logTrace ( [$trace] ); =item $level = logLevel ( [$level] ); =item $termLevel = logTermLevel ( [$termLevel] ); =item $databaseLevel = logDatabaseLevel ( [$databaseLevel] ); =item $fileLevel = logFileLevel ( [$fileLevel] ); =item $syslogLevel = logSyslogLevel ( [$syslogLevel] ); These methods can be used to get and set the current settings as defined in logInit. =item Debug( $string ); This method will output a debug message if the current debug level permits it, otherwise does nothing. This message will be tagged with the DBG string in the logs. =item Info( $string ); This method will output an informational message if the current debug level permits it, otherwise does nothing. This message will be tagged with the INF string in the logs. =item Warning( $string ); This method will output a warning message if the current debug level permits it, otherwise does nothing. This message will be tagged with the WAR string in the logs. =item Error( $string ); This method will output an error message if the current debug level permits it, otherwise does nothing. This message will be tagged with the ERR string in the logs. =item Fatal( $string ); This method will output a fatal error message and then die if the current debug level permits it, otherwise does nothing. This message will be tagged with the FAT string in the logs. =item Panic( $string ); This method will output a panic error message and then die with a stack trace if the current debug level permits it, otherwise does nothing. This message will be tagged with the PNC string in the logs. =back =head2 EXPORT None by default. The :constants tag will export the debug constants which define the various levels of debug The :variables tag will export variables containing the current debug id and level The :functions tag will export the debug functions. This or :all is what you would normally use. The :all tag will export all above symbols. =head1 SEE ALSO Carp Sys::Syslog The ZoneMinder README file Troubleshooting section for an extended discussion on the use and configuration of syslog with ZoneMinder. http://www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm0000644000000000000000000000443613365153155022733 0ustar rootroot# ========================================================================== # # ZoneMinder Monitor Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Monitor; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Object; require ZoneMinder::Storage; require ZoneMinder::Server; #our @ISA = qw(Exporter ZoneMinder::Base); use parent qw(ZoneMinder::Object); # ========================================================================== # # General Utility Functions # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); use POSIX; use vars qw/ $table $primary_key /; $table = 'Monitors'; $primary_key = 'Id'; sub Server { return new ZoneMinder::Server( $_[0]{ServerId} ); } # end sub Server sub Storage { return new ZoneMinder::Storage( $_[0]{StorageId} ); } # end sub Storage 1; __END__ =head1 NAME ZoneMinder::Monitor - Perl Class for Monitors =head1 SYNOPSIS use ZoneMinder::Monitor; =head1 AUTHOR Isaac Connor, Eisaac@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2017 ZoneMinder LLC This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Memory/0000755000000000000000000000000013365153155022207 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm0000644000000000000000000001132113365153155023751 0ustar rootroot# ========================================================================== # # ZoneMinder Shared Memory Access Module, $Date: 2007-08-29 19:11:09 +0100 (Wed, 29 Aug 2007) $, $Revision: 2175 $ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Memory::Shared; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter ZoneMinder::Base); eval 'sub IPC_CREAT {0001000}' unless defined &IPC_CREAT; # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( zmMemKey zmMemAttach zmMemDetach zmMemGet zmMemPut zmMemClean ) ], ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = @EXPORT_OK; our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Shared Memory Facilities # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); sub zmMemKey { my $monitor = shift; return( defined($monitor->{ShmKey})?$monitor->{ShmKey}:undef ); } sub zmMemAttach { my $monitor = shift; my $size = shift; if ( !defined($monitor->{ShmId}) ) { my $shm_key = (hex($Config{ZM_SHM_KEY})&0xffff0000)|$monitor->{Id}; my $shm_id = shmget( $shm_key, $size, &IPC_CREAT | 0777 ); if ( !defined($shm_id) ) { Error( sprintf( "Can't get shared memory id '%x', %d: $!\n" , $shm_key , $monitor->{Id} ) ); return( undef ); } $monitor->{ShmKey} = $shm_key; $monitor->{ShmId} = $shm_id; } return( !undef ); } sub zmMemDetach { my $monitor = shift; delete $monitor->{ShmId}; } sub zmMemGet { my $monitor = shift; my $offset = shift; my $size = shift; my $shm_key = $monitor->{ShmKey}; my $shm_id = $monitor->{ShmId}; my $data; if ( !shmread( $shm_id, $data, $offset, $size ) ) { Error( sprintf( "Can't read from shared memory '%x/%d': $!" , $shm_key , $shm_id ) ); return( undef ); } return( $data ); } sub zmMemPut { my $monitor = shift; my $offset = shift; my $size = shift; my $data = shift; my $shm_key = $monitor->{ShmKey}; my $shm_id = $monitor->{ShmId}; if ( !shmwrite( $shm_id, $data, $offset, $size ) ) { Error( sprintf( "Can't write to shared memory '%x/%d': $!" , $shm_key , $shm_id ) ); return( undef ); } return( !undef ); } sub zmMemClean { Debug( "Removing shared memory\n" ); # Find ZoneMinder shared memory my $command = "ipcs -m | grep '^" .substr( sprintf( "0x%x", hex($Config{ZM_SHM_KEY}) ), 0, -2 ) ."'" ; Debug( "Checking for shared memory with '$command'\n" ); open( my $CMD, '<', "$command |" ) or Fatal( "Can't execute '$command': $!" ); while( <$CMD> ) { chomp; my ( $key, $id ) = split( /\s+/ ); if ( $id =~ /^(\d+)/ ) { $id = $1; $command = "ipcrm shm $id"; Debug( "Removing shared memory with '$command'\n" ); qx( $command ); } } close( $CMD ); } 1; __END__ ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm0000644000000000000000000001172213365153155023756 0ustar rootroot# ========================================================================== # # ZoneMinder Mapped Memory Access Module, $Date: 2008-02-25 10:13:13 +0000 (Mon, 25 Feb 2008) $, $Revision: 2323 $ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the definitions and functions used when accessing mapped memory # package ZoneMinder::Memory::Mapped; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter ZoneMinder::Base); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( functions => [ qw( zmMemKey zmMemAttach zmMemDetach zmMemGet zmMemPut zmMemClean ) ], ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = @EXPORT_OK; our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Mapped Memory Facilities # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use Sys::Mmap; sub zmMemKey { my $monitor = shift; return $monitor->{MMapAddr}; } sub zmMemAttach { my ( $monitor, $size ) = @_; if ( ! $size ) { Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); return undef; } if ( !defined($monitor->{MMapAddr}) ) { my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id}; if ( ! -e $mmap_file ) { Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." , $mmap_file ) ); return undef; } my $mmap_file_size = -s $mmap_file; if ( $mmap_file_size < $size ) { Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" , $mmap_file , $size , $mmap_file_size ) ); return undef; } my $MMAP; if ( !open( $MMAP, '+<', $mmap_file ) ) { Error( sprintf( "Can't open memory map file '%s': $!", $mmap_file ) ); return undef; } my $mmap = undef; my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP ); if ( !$mmap_addr || !$mmap ) { Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); close( $MMAP ); return undef; } $monitor->{MMapHandle} = $MMAP; $monitor->{MMapAddr} = $mmap_addr; $monitor->{MMap} = \$mmap; } return !undef; } sub zmMemDetach { my $monitor = shift; if ( $monitor->{MMap} ) { if ( ! munmap( ${$monitor->{MMap}} ) ) { Warn( "Unable to munmap for monitor $$monitor{Id}\n"); } delete $monitor->{MMap}; } if ( $monitor->{MMapAddr} ) { delete $monitor->{MMapAddr}; } if ( $monitor->{MMapHandle} ) { close( $monitor->{MMapHandle} ); delete $monitor->{MMapHandle}; } } sub zmMemGet { my $monitor = shift; my $offset = shift; my $size = shift; my $mmap = $monitor->{MMap}; if ( !$mmap || !$$mmap ) { Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" , $monitor->{Id} ) ); return undef; } my $data = substr( $$mmap, $offset, $size ); return $data; } sub zmMemPut { my $monitor = shift; my $offset = shift; my $size = shift; my $data = shift; my $mmap = $monitor->{MMap}; if ( !$mmap || !$$mmap ) { Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" , $monitor->{Id} ) ); return( undef ); } substr( $$mmap, $offset, $size ) = $data; return( !undef ); } sub zmMemClean { Debug( "Removing memory map files\n" ); my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; foreach my $mapFile( glob( $mapPath ) ) { ( $mapFile ) = $mapFile =~ /^(.+)$/; Debug( "Removing memory map file '$mapFile'\n" ); unlink( $mapFile ); } } 1; __END__ ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm0000644000000000000000000003735513365153155022537 0ustar rootroot# ========================================================================== # # ZoneMinder Filter Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Filter; use 5.006; use strict; use warnings; require ZoneMinder::Base; require Date::Manip; use parent qw(ZoneMinder::Object); use vars qw/ $table $primary_key /; $table = 'Filters'; $primary_key = 'Id'; # ========================================================================== # # General Utility Functions # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); require ZoneMinder::Storage; require ZoneMinder::Server; use POSIX; sub Name { if ( @_ > 1 ) { $_[0]{Name} = $_[1]; } return $_[0]{Name}; } # end sub Path sub find { shift if $_[0] eq 'ZoneMinder::Filter'; my %sql_filters = @_; my $sql = 'SELECT * FROM Filters'; my @sql_filters; my @sql_values; if ( exists $sql_filters{Name} ) { push @sql_filters , ' Name = ? '; push @sql_values, $sql_filters{Name}; } $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); my $res = $sth->execute( @sql_values ) or Fatal( "Can't execute '$sql': ".$sth->errstr() ); my @results; while( my $db_filter = $sth->fetchrow_hashref() ) { my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while $sth->finish(); return @results; } sub find_one { my @results = find(@_); return $results[0] if @results; } sub Execute { my $self = $_[0]; my $sql = $self->Sql(undef); if ( $self->{HasDiskPercent} ) { my $disk_percent = getDiskPercent( $$self{Storage} ? $$self{Storage}->Path() : () ); $sql =~ s/zmDiskPercent/$disk_percent/g; } if ( $self->{HasDiskBlocks} ) { my $disk_blocks = getDiskBlocks(); $sql =~ s/zmDiskBlocks/$disk_blocks/g; } if ( $self->{HasSystemLoad} ) { my $load = getLoad(); $sql =~ s/zmSystemLoad/$load/g; } Debug("Filter::Execute SQL ($sql)"); my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); my $res = $sth->execute(); if ( !$res ) { Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); return; } my @results; while( my $event = $sth->fetchrow_hashref() ) { push @results, $event; } $sth->finish(); Debug('Loaded ' . @results . " events for filter $_[0]{Name} using query ($sql)"); return @results; } sub Sql { my $self = shift; $$self{Sql} = shift if @_; if ( ! $$self{Sql} ) { my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query}); my $sql = 'SELECT E.*, unix_timestamp(E.StartTime) as Time, M.Name as MonitorName, M.DefaultRate, M.DefaultScale FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId LEFT JOIN Storage as S on S.Id = E.StorageId '; $self->{Sql} = ''; if ( $filter_expr->{terms} ) { foreach my $term ( @{$filter_expr->{terms}} ) { if ( exists($term->{cnj}) ) { $self->{Sql} .= ' '.$term->{cnj}.' '; } if ( exists($term->{obr}) ) { $self->{Sql} .= ' '.str_repeat( '(', $term->{obr} ).' '; } my $value = $term->{val}; my @value_list; if ( $term->{attr} ) { if ( $term->{attr} =~ /^Monitor/ ) { my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; $self->{Sql} .= 'M.'.$temp_attr_name; } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { $self->{Sql} .= 'M.ServerId'; } elsif ( $term->{attr} eq 'StorageServerId' ) { $self->{Sql} .= 'S.ServerId'; } elsif ( $term->{attr} eq 'FilterServerId' ) { $self->{Sql} .= $Config{ZM_SERVER_ID}; # StartTime options } elsif ( $term->{attr} eq 'DateTime' ) { $self->{Sql} .= 'E.StartTime'; } elsif ( $term->{attr} eq 'StartDateTime' ) { $self->{Sql} .= 'E.StartTime'; } elsif ( $term->{attr} eq 'Date' ) { $self->{Sql} .= 'to_days( E.StartTime )'; } elsif ( $term->{attr} eq 'StartDate' ) { $self->{Sql} .= 'to_days( E.StartTime )'; } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { $self->{Sql} .= 'extract( hour_second from E.StartTime )'; } elsif ( $term->{attr} eq 'Weekday' ) { $self->{Sql} .= 'weekday( E.StartTime )'; # EndTIme options } elsif ( $term->{attr} eq 'EndDateTime' ) { $self->{Sql} .= 'E.EndTime'; } elsif ( $term->{attr} eq 'EndDate' ) { $self->{Sql} .= 'to_days( E.EndTime )'; } elsif ( $term->{attr} eq 'EndTime' ) { $self->{Sql} .= 'extract( hour_second from E.EndTime )'; } elsif ( $term->{attr} eq 'EndWeekday' ) { $self->{Sql} .= "weekday( E.EndTime )"; # } elsif ( $term->{attr} eq 'DiskSpace' ) { $self->{Sql} .= 'E.DiskSpace'; $self->{HasDiskPercent} = !undef; } elsif ( $term->{attr} eq 'DiskPercent' ) { $self->{Sql} .= 'zmDiskPercent'; $self->{HasDiskPercent} = !undef; } elsif ( $term->{attr} eq 'DiskBlocks' ) { $self->{Sql} .= 'zmDiskBlocks'; $self->{HasDiskBlocks} = !undef; } elsif ( $term->{attr} eq 'SystemLoad' ) { $self->{Sql} .= 'zmSystemLoad'; $self->{HasSystemLoad} = !undef; } else { $self->{Sql} .= 'E.'.$term->{attr}; } ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { if ( $term->{attr} =~ /^MonitorName/ ) { $value = "'$temp_value'"; } elsif ( $term->{attr} =~ /ServerId/) { Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); if ( $temp_value eq 'ZM_SERVER_ID' ) { $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; # This gets used later, I forget for what $$self{Server} = new ZoneMinder::Server( $ZoneMinder::Config::Config{ZM_SERVER_ID} ); } elsif ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { $value = "'$temp_value'"; # This gets used later, I forget for what $$self{Server} = new ZoneMinder::Server( $temp_value ); } } elsif ( $term->{attr} eq 'StorageId' ) { $value = "'$temp_value'"; $$self{Storage} = new ZoneMinder::Storage( $temp_value ); } elsif ( $term->{attr} eq 'Name' || $term->{attr} eq 'Cause' || $term->{attr} eq 'Notes' ) { $value = "'$temp_value'"; } elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) { if ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " ."skipping filter '$self->{Name}'\n" ); return; } $value = "'$value'"; } } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { if ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " ."skipping filter '$self->{Name}'\n" ); return; } $value = "to_days( '$value' )"; } } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) { if ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " ."skipping filter '$self->{Name}'\n" ); return; } $value = "extract( hour_second from '$value' )"; } } else { $value = $temp_value; } push( @value_list, $value ); } # end foreach temp_value } # end if has an attr if ( $term->{op} ) { if ( $term->{op} eq '=~' ) { $self->{Sql} .= " regexp $value"; } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not regexp $value"; } elsif ( $term->{op} eq 'IS' ) { if ( $value eq 'Odd' ) { $self->{Sql} .= ' % 2 = 1'; } elsif ( $value eq 'Even' ) { $self->{Sql} .= ' % 2 = 0'; } else { $self->{Sql} .= " IS $value"; } } elsif ( $term->{op} eq 'IS NOT' ) { $self->{Sql} .= " IS NOT $value"; } elsif ( $term->{op} eq '=[]' ) { $self->{Sql} .= " in (".join( ",", @value_list ).")"; } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not in (".join( ",", @value_list ).")"; } else { $self->{Sql} .= ' '.$term->{op}." $value"; } } # end if has an operator if ( exists($term->{cbr}) ) { $self->{Sql} .= ' '.str_repeat( ")", $term->{cbr} )." "; } } # end foreach term } # end if terms if ( $self->{Sql} ) { if ( $self->{AutoMessage} ) { # Include all events, including events that are still ongoing # and have no EndTime yet $sql .= ' WHERE ( '.$self->{Sql}.' )'; } else { # Only include closed events (events with valid EndTime) $sql .= ' WHERE (E.EndTime IS NOT NULL) AND ( '.$self->{Sql}.' )'; } } my @auto_terms; if ( $self->{AutoArchive} ) { push @auto_terms, 'E.Archived = 0'; } # Don't do this, it prevents re-generation and concatenation. # If the file already exists, then the video won't be re-recreated if ( $self->{AutoVideo} ) { push @auto_terms, "E.Videoed = 0"; } if ( $self->{AutoUpload} ) { push @auto_terms, "E.Uploaded = 0"; } if ( $self->{AutoEmail} ) { push @auto_terms, "E.Emailed = 0"; } if ( $self->{AutoMessage} ) { push @auto_terms, "E.Messaged = 0"; } if ( $self->{AutoExecute} ) { push @auto_terms, "E.Executed = 0"; } if ( @auto_terms ) { $sql .= " and ( ".join( ' or ', @auto_terms )." )"; } if ( !$filter_expr->{sort_field} ) { $filter_expr->{sort_field} = 'StartTime'; $filter_expr->{sort_asc} = 0; } my $sort_column = ''; if ( $filter_expr->{sort_field} eq 'Id' ) { $sort_column = 'E.Id'; } elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) { $sort_column = 'M.Name'; } elsif ( $filter_expr->{sort_field} eq 'Name' ) { $sort_column = 'E.Name'; } elsif ( $filter_expr->{sort_field} eq 'StartTime' ) { $sort_column = 'E.StartTime'; } elsif ( $filter_expr->{sort_field} eq 'EndTime' ) { $sort_column = 'E.EndTime'; } elsif ( $filter_expr->{sort_field} eq 'Secs' ) { $sort_column = 'E.Length'; } elsif ( $filter_expr->{sort_field} eq 'Frames' ) { $sort_column = 'E.Frames'; } elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) { $sort_column = 'E.AlarmFrames'; } elsif ( $filter_expr->{sort_field} eq 'TotScore' ) { $sort_column = 'E.TotScore'; } elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) { $sort_column = 'E.AvgScore'; } elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) { $sort_column = 'E.MaxScore'; } elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) { $sort_column = 'E.DiskSpace'; } else { $sort_column = 'E.StartTime'; } my $sort_order = $filter_expr->{sort_asc}?'asc':'desc'; $sql .= ' order by '.$sort_column." ".$sort_order; if ( $filter_expr->{limit} ) { $sql .= " limit 0,".$filter_expr->{limit}; } $self->{Sql} = $sql; } # end if has Sql return $self->{Sql}; } # end sub Sql sub getDiskPercent { my $command = 'df ' . ($_[0] ? $_[0] : '.'); my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)%/ms ) { $space = $1; } return( $space ); } sub getDiskBlocks { my $command = 'df .'; my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) { $space = $1; } return( $space ); } sub getLoad { my $command = 'uptime .'; my $uptime = qx( $command ); my $load = -1; if ( $uptime =~ /load average:\s+([\d.]+)/ms ) { $load = $1; Info( "Load: $load" ); } return( $load ); } # # More or less replicates the equivalent PHP function # sub strtotime { my $dt_str = shift; return( Date::Manip::UnixDate( $dt_str, '%s' ) ); } # # More or less replicates the equivalent PHP function # sub str_repeat { my $string = shift; my $count = shift; return( ${string}x${count} ); } # Formats a date into MySQL format sub DateTimeToSQL { my $dt_str = shift; my $dt_val = strtotime( $dt_str ); if ( !$dt_val ) { Error( "Unable to parse date string '$dt_str'\n" ); return( undef ); } return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Filter; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Control.pm0000644000000000000000000001065713365153155022726 0ustar rootroot# ========================================================================== # # ZoneMinder Base Control Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the base class definitions for the camera control # protocol implementations # package ZoneMinder::Control; use 5.006; use strict; use warnings; require ZoneMinder::Base; our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Base connection class # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); our $AUTOLOAD; sub new { my $class = shift; my $id = shift; my $self = {}; $self->{name} = $class; if ( !defined($id) ) { Fatal('No monitor defined when invoking protocol '.$self->{name}); } $self->{id} = $id; bless( $self, $class ); return $self; } sub DESTROY { } sub AUTOLOAD { my $self = shift; my $class = ref($self) || Fatal("$self not object"); my $name = $AUTOLOAD; $name =~ s/.*://; if ( exists($self->{$name}) ) { return $self->{$name}; } Error("Can't access $name member of object of class $class"); } sub getKey { my $self = shift; return( $self->{id} ); } sub open { my $self = shift; Fatal( "No open method defined for protocol ".$self->{name} ); } sub close { my $self = shift; $self->{state} = 'closed'; Debug('No close method defined for protocol '.$self->{name}); } sub loadMonitor { my $self = shift; if ( !$self->{Monitor} ) { if ( !($self->{Monitor} = zmDbGetMonitor( $self->{id} )) ) { Fatal( "Monitor id ".$self->{id}." not found or not controllable" ); } if ( defined($self->{Monitor}->{AutoStopTimeout}) ) { # Convert to microseconds. $self->{Monitor}->{AutoStopTimeout} = int(1000000*$self->{Monitor}->{AutoStopTimeout}); } } } sub getParam { my $self = shift; my $params = shift; my $name = shift; my $default = shift; if ( defined($params->{$name}) ) { return( $params->{$name} ); } elsif ( defined($default) ) { return( $default ); } Fatal( "Missing mandatory parameter '$name'" ); } sub executeCommand { my $self = shift; my $params = shift; $self->loadMonitor(); my $command = $params->{command}; delete $params->{command}; #if ( !defined($self->{$command}) ) #{ #Fatal( "Unsupported command '$command'" ); #} &{$self->{$command}}( $self, $params ); } sub printMsg { my $self = shift; Fatal( "No printMsg method defined for protocol ".$self->{name} ); } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Database; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Server.pm0000644000000000000000000001076513365153155022554 0ustar rootroot# ========================================================================== # # ZoneMinder Server Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Server; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter ZoneMinder::Base); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( CpuLoad ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # General Utility Functions # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); use POSIX; sub new { my ( $parent, $id, $data ) = @_; my $self = {}; bless $self, $parent; if ( ( $$self{Id} = $id ) or $data ) { #$log->debug("loading $parent $id") if $debug or DEBUG_ALL; $self->load( $data ); } return $self; } # end sub new sub load { my ( $self, $data ) = @_; my $type = ref $self; if ( ! $data ) { #$log->debug("Object::load Loading from db $type"); $data = $ZoneMinder::Database::dbh->selectrow_hashref( 'SELECT * FROM Servers WHERE Id=?', {}, $$self{Id} ); if ( ! $data ) { if ( $ZoneMinder::Database::dbh->errstr ) { Error( "Failure to load Server record for $$self{id}: Reason: " . $ZoneMinder::Database::dbh->errstr ); } # end if } # end if } # end if ! $data if ( $data and %$data ) { @$self{keys %$data} = values %$data; } # end if } # end sub load sub Name { if ( @_ > 1 ) { $_[0]{Name} = $_[1]; } return $_[0]{Name}; } # end sub Name sub Hostname { if ( @_ > 1 ) { $_[0]{Hostname} = $_[1]; } return $_[0]{Hostname}; } # end sub Hostname sub CpuLoad { my $output = qx(uptime); my @sysloads = split ', ', (split ': ', $output)[-1]; if (join(', ',@sysloads) =~ /(\d+\.\d+)\s*,\s+(\d+\.\d+)\s*,\s+(\d+\.\d+)\s*$/) { return @sysloads; } return (undef, undef, undef); } # end sub CpuLoad 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS use ZoneMinder::Server; blah blah blah =head1 DESCRIPTION Stub documentation for ZoneMinder, created by h2xs. It looks like the author of the extension was negligent enough to leave the stub unedited. Blah blah blah. =head2 EXPORT None by default. =head1 SEE ALSO Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. If you have a web site set up for your module, mention it here. =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in0000644000000000000000000000370413365153155022560 0ustar rootrootpackage ZoneMinder::Base; use 5.006; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); use constant ZM_VERSION => '@VERSION@'; # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'all' => [ qw(ZM_VERSION) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw(); our $VERSION = '@VERSION@'; 1; __END__ =head1 NAME ZoneMinder::Base - Base perl module for ZoneMinder =head1 SYNOPSIS use ZoneMinder::Base; =head1 DESCRIPTION This module is the base module for the rest of the ZoneMinder modules. It is included by each of the other modules but serves no purpose other than to propagate the perl module version amongst the other modules. You will never need to use this module directly but if you write new ZoneMinder modules they should include it. =head2 EXPORT None by default. =head1 SEE ALSO http://www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE 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. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Event.pm0000644000000000000000000005120713365153155022363 0ustar rootroot# ========================================================================== # # ZoneMinder Event Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Event; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Object; require ZoneMinder::Storage; require Date::Manip; require File::Find; require File::Path; require File::Copy; require File::Basename; require Number::Bytes::Human; #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); # ========================================================================== # # General Utility Functions # # ========================================================================== use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); require Date::Parse; use vars qw/ $table $primary_key %fields $serial @identified_by/; $table = 'Events'; @identified_by = ('Id'); $serial = $primary_key = 'Id'; %fields = map { $_, $_ } qw( Id MonitorId StorageId Name Cause StartTime EndTime Width Height Length Frames AlarmFrames DefaultVideo TotScore AvgScore MaxScore Archived Videoed Uploaded Emailed Messaged Executed Notes StateId Orientation DiskSpace ); use POSIX; sub Time { if ( @_ > 1 ) { $_[0]{Time} = $_[1]; } if ( ! defined $_[0]{Time} ) { if ( $_[0]{StartTime} ) { $_[0]{Time} = Date::Parse::str2time( $_[0]{StartTime} ); } } return $_[0]{Time}; } sub Name { if ( @_ > 1 ) { $_[0]{Name} = $_[1]; } return $_[0]{Name}; } # end sub Name sub find { shift if $_[0] eq 'ZoneMinder::Event'; my %sql_filters = @_; my $sql = 'SELECT * FROM Events'; my @sql_filters; my @sql_values; if ( exists $sql_filters{Name} ) { push @sql_filters , ' Name = ? '; push @sql_values, $sql_filters{Name}; } if ( exists $sql_filters{Id} ) { push @sql_filters , ' Id = ? '; push @sql_values, $sql_filters{Id}; } $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); my $res = $sth->execute( @sql_values ) or Fatal( "Can't execute '$sql': ".$sth->errstr() ); my @results; while( my $db_filter = $sth->fetchrow_hashref() ) { my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while $sth->finish(); return @results; } sub find_one { my @results = find(@_); return $results[0] if @results; } sub getPath { return Path( @_ ); } sub Path { my $event = shift; if ( @_ ) { $$event{Path} = $_[0]; if ( $$event{Path} and ! -e $$event{Path} ) { Error("Setting path for event $$event{Id} to $_[0] but does not exist!"); } } if ( ! $$event{Path} ) { my $Storage = $event->Storage(); $$event{Path} = join('/', $Storage->Path(), $event->RelativePath() ); } return $$event{Path}; } sub Scheme { my $self = shift; if ( ! $$self{Scheme} ) { if ( $$self{RelativePath} ) { if ( $$self{RelativePath} =~ /^\d+\/\d{4}\-\d{2}\-\d{2}\/\d+$/ ) { $$self{Scheme} = 'Medium'; } elsif ( $$self{RelativePath} =~ /^\d+\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/$/ ) { $$self{Scheme} = 'Deep'; } } # end if RelativePath } return $$self{Scheme}; } sub RelativePath { my $event = shift; if ( @_ ) { $$event{RelativePath} = $_[0]; } if ( ! $$event{RelativePath} ) { if ( $$event{Scheme} eq 'Deep' ) { if ( $event->Time() ) { $$event{RelativePath} = join('/', $event->{MonitorId}, strftime( '%y/%m/%d/%H/%M/%S', localtime($event->Time()) ), ); } else { Error("Event $$event{Id} has no value for Time(), unable to determine path"); $$event{RelativePath} = ''; } } elsif ( $$event{Scheme} eq 'Medium' ) { if ( $event->Time() ) { $$event{RelativePath} = join('/', $event->{MonitorId}, strftime( '%Y-%m-%d', localtime($event->Time())), $event->{Id}, ); } else { Error("Event $$event{Id} has no value for Time(), unable to determine path"); $$event{RelativePath} = ''; } } else { # Shallow $$event{RelativePath} = join('/', $event->{MonitorId}, $event->{Id}, ); } # end if Scheme } # end if ! Path return $$event{RelativePath}; } sub LinkPath { my $event = shift; if ( @_ ) { $$event{LinkPath} = $_[0]; } if ( ! $$event{LinkPath} ) { if ( $$event{Scheme} eq 'Deep' ) { if ( $event->Time() ) { $$event{LinkPath} = join('/', $event->{MonitorId}, strftime( '%y/%m/%d', localtime($event->Time()) ), '.'.$$event{Id} ); } elsif ( $$event{Path} ) { if ( ( $event->RelativePath() =~ /^(\d+\/\d{4}\/\d{2}\/\d{2})/ ) ) { $$event{LinkPath} = $1.'/.'.$$event{Id}; } else { Error("Unable to get LinkPath from Path for $$event{Id} $$event{Path}"); $$event{LinkPath} = ''; } } else { Error("Event $$event{Id} $$event{Path} has no value for Time(), unable to determine link path"); $$event{LinkPath} = ''; } } # end if Scheme } # end if ! Path return $$event{LinkPath}; } # end sub LinkPath sub createPath { makePath($_[0]->Path()); } sub createLinkPath { my $LinkPath = $_[0]->LinkPath(); my $EventPath = $_[0]->EventPath(); if ( $LinkPath ) { if ( !symlink($EventPath, $LinkPath) ) { Error("Failed symlinking $EventPath to $LinkPath"); } } } sub idPath { return sprintf('%s/.%d', $_[0]->Path(), $_[0]->{Id}); } sub createIdFile { my $event = shift; my $idFile = $event->idPath(); open( my $ID_FP, '>', $idFile ) or Error("Can't open $idFile: $!"); close($ID_FP); setFileOwner($idFile); } sub GenerateVideo { my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; my $event_path = $self->Path( ); chdir( $event_path ); ( my $video_name = $self->{Name} ) =~ s/\s/_/g; my @file_parts; if ( $rate ) { my $file_rate = $rate; $file_rate =~ s/\./_/; $file_rate =~ s/_00//; $file_rate =~ s/(_\d+)0+$/$1/; $file_rate = 'r'.$file_rate; push( @file_parts, $file_rate ); } elsif ( $fps ) { my $file_fps = $fps; $file_fps =~ s/\./_/; $file_fps =~ s/_00//; $file_fps =~ s/(_\d+)0+$/$1/; $file_fps = 'R'.$file_fps; push( @file_parts, $file_fps ); } if ( $scale ) { my $file_scale = $scale; $file_scale =~ s/\./_/; $file_scale =~ s/_00//; $file_scale =~ s/(_\d+)0+$/$1/; $file_scale = 's'.$file_scale; push( @file_parts, $file_scale ); } elsif ( $size ) { my $file_size = 'S'.$size; push( @file_parts, $file_size ); } my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; if ( $overwrite || !-s $video_file ) { Info( "Creating video file $video_file for event $self->{Id}\n" ); my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} ); if ( $rate ) { if ( $rate != 1.0 ) { $frame_rate *= $rate; } } elsif ( $fps ) { $frame_rate = $fps; } my $width = $self->{Width}; my $height = $self->{Height}; my $video_size = " ${width}x${height}"; if ( $scale ) { if ( $scale != 1.0 ) { $width = int($width*$scale); $height = int($height*$scale); $video_size = " ${width}x${height}"; } } elsif ( $size ) { $video_size = $size; } my $command = $Config{ZM_PATH_FFMPEG} ." -y -r $frame_rate " .$Config{ZM_FFMPEG_INPUT_OPTIONS} .' -i ' . ( $$self{DefaultVideo} ? $$self{DefaultVideo} : '%0'.$Config{ZM_EVENT_IMAGE_DIGITS} .'d-capture.jpg' ) #. " -f concat -i /tmp/event_files.txt" ." -s $video_size " .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} ." '$video_file' > ffmpeg.log 2>&1" ; Debug( $command."\n" ); my $output = qx($command); my $status = $? >> 8; if ( $status ) { Error( "Unable to generate video, check $event_path/ffmpeg.log for details"); return; } Info( "Finished $video_file\n" ); return $event_path.'/'.$video_file; } else { Info( "Video file $video_file already exists for event $self->{Id}\n" ); return $event_path.'/'.$video_file; } return; } # end sub GenerateVideo sub delete { my $event = $_[0]; if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) { my ( $caller, undef, $line ) = caller; Warning("Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n"); return; } if ( ! -e $event->Storage()->Path() ) { Warning("Not deleting event because storage path doesn't exist"); return; } Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n"); $ZoneMinder::Database::dbh->ping(); $ZoneMinder::Database::dbh->begin_work(); #$event->lock_and_load(); { my $sql = 'DELETE FROM Frames WHERE EventId=?'; my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); my $res = $sth->execute($event->{Id}) or Error( "Can't execute '$sql': ".$sth->errstr() ); $sth->finish(); if ( $ZoneMinder::Database::dbh->errstr() ) { $ZoneMinder::Database::dbh->commit(); return; } $sql = 'DELETE FROM Stats WHERE EventId=?'; $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr()); $res = $sth->execute($event->{Id}) or Error("Can't execute '$sql': ".$sth->errstr()); $sth->finish(); if ( $ZoneMinder::Database::dbh->errstr() ) { $ZoneMinder::Database::dbh->commit(); return; } } # Do it individually to avoid locking up the table for new events { my $sql = 'DELETE FROM Events WHERE Id=?'; my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr()); my $res = $sth->execute($event->{Id}) or Error("Can't execute '$sql': ".$sth->errstr()); $sth->finish(); } $ZoneMinder::Database::dbh->commit(); if ( (! $Config{ZM_OPT_FAST_DELETE}) and $event->Storage()->DoDelete() ) { $event->delete_files( ); } else { Debug('Not deleting event files from '.$event->Path().' for speed.'); } } # end sub delete sub delete_files { my $event = shift; my $Storage = @_ ? $_[0] : new ZoneMinder::Storage( $$event{StorageId} ); my $storage_path = $Storage->Path(); if ( ! $storage_path ) { Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId} "); return; } if ( ! $$event{MonitorId} ) { Error("No monitor id assigned to event $$event{Id}"); return; } my $event_path = $event->RelativePath(); Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}."); if ( $event_path ) { ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint my $deleted = 0; if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) { my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); eval { require Net::Amazon::S3; my $s3 = Net::Amazon::S3->new( { aws_access_key_id => $aws_id, aws_secret_access_key => $aws_secret, ( $aws_host ? ( host => $aws_host ) : () ), }); my $bucket = $s3->bucket($aws_bucket); if ( ! $bucket ) { Error("S3 bucket $bucket not found."); die; } if ( $bucket->delete_key($event_path) ) { $deleted = 1; } else { Error("Failed to delete from S3:".$s3->err . ": " . $s3->errstr); } }; Error($@) if $@; } if ( ! $deleted ) { my $command = "/bin/rm -rf $storage_path/$event_path"; ZoneMinder::General::executeShellCommand( $command ); } } if ( $event->Scheme() eq 'Deep' ) { my $link_path = $event->LinkPath(); Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path."); if ( $link_path ) { ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint unlink($storage_path.'/'.$link_path) or Error( "Unable to unlink '$storage_path/$link_path': $!" ); } } } # end sub delete_files sub Storage { if ( @_ > 1 ) { $_[0]{Storage} = $_[1]; } if ( ! $_[0]{Storage} ) { $_[0]{Storage} = new ZoneMinder::Storage($_[0]{StorageId}); } return $_[0]{Storage}; } sub check_for_in_filesystem { my $path = $_[0]->Path(); if ( $path ) { if ( -e $path ) { my @files = glob "$path/*"; Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files"); return 1 if @files; } else { Warning("Path not found for Event $_[0]{Id} at $path"); } } Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found no files"); return 0; } sub age { if ( ! $_[0]{age} ) { if ( -e $_[0]->Path() ) { # $^T is the time the program began running. -M is program start time - file modification time in days $_[0]{age} = (time() - ($^T - ((-M $_[0]->Path() ) * 24*60*60))); } else { Warning($_[0]->Path() . ' does not appear to exist.'); } } return $_[0]{age}; } sub DiskSpace { if ( @_ > 1 ) { Debug("Cleared DiskSpace, was $_[0]{DiskSpace}") if $_[0]{DiskSpace}; $_[0]{DiskSpace} = $_[1]; } if ( ! defined $_[0]{DiskSpace} ) { if ( -e $_[0]->Path() ) { my $size = 0; File::Find::find( { wanted=>sub { $size += -f $_ ? -s _ : 0 }, untaint=>1 }, $_[0]->Path() ); $_[0]{DiskSpace} = $size; Debug("DiskSpace for event $_[0]{Id} at $_[0]{Path} Updated to $size bytes"); } else { Warning("DiskSpace: Event does not exist at $_[0]{Path}:" . $_[0]->to_string() ); } } # end if ! defined DiskSpace return $_[0]{DiskSpace}; } sub MoveTo { my ( $self, $NewStorage ) = @_; my $OldStorage = $self->Storage(undef); my ( $OldPath ) = ( $self->Path() =~ /^(.*)$/ ); # De-taint if ( ! -e $OldPath ) { return "Old path $OldPath does not exist."; } # First determine if we can move it to the dest. # We do this before bothering to lock the event my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint if ( ! $$NewStorage{Id} ) { return "New storage does not have an id. Moving will not happen."; } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { return "Event is already located at " . $NewPath; } elsif ( !$NewPath ) { return "New path ($NewPath) is empty."; } elsif ( ! -e $NewPath ) { return "New path $NewPath does not exist."; } $ZoneMinder::Database::dbh->begin_work(); $self->lock_and_load(); # data is reloaded, so need to check that the move hasn't already happened. if ( $$self{StorageId} == $$NewStorage{Id} ) { $ZoneMinder::Database::dbh->commit(); return "Event has already been moved by someone else."; } if ( $$OldStorage{Id} != $$self{StorageId} ) { $ZoneMinder::Database::dbh->commit(); return "Old Storage path changed, Event has moved somewhere else."; } $$self{Storage} = $NewStorage; ( $NewPath ) = ( $self->Path(undef) =~ /^(.*)$/ ); # De-taint if ( $NewPath eq $OldPath ) { $ZoneMinder::Database::dbh->commit(); return "New path and old path are the same! $NewPath"; } Debug("Moving event $$self{Id} from $OldPath to $NewPath"); my $moved = 0; if ( $$NewStorage{Type} eq 's3fs' ) { my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$NewStorage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); eval { require Net::Amazon::S3; require File::Slurp; my $s3 = Net::Amazon::S3->new( { aws_access_key_id => $aws_id, aws_secret_access_key => $aws_secret, ( $aws_host ? ( host => $aws_host ) : () ), }); my $bucket = $s3->bucket($aws_bucket); if ( ! $bucket ) { Error("S3 bucket $bucket not found."); die; } my $event_path = 'events/'.$self->RelativePath(); Info("Making dir ectory $event_path/"); if ( ! $bucket->add_key( $event_path.'/','' ) ) { die "Unable to add key for $event_path/"; } my @files = glob("$OldPath/*"); Debug("Files to move @files"); for my $file (@files) { next if $file =~ /^\./; ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint my $starttime = time; Debug("Moving file $file to $NewPath"); my $size = -s $file; if ( ! $size ) { Info("Not moving file with 0 size"); } my $file_contents = File::Slurp::read_file($file); if ( ! $file_contents ) { die "Loaded empty file, but it had a size. Giving up"; } my $filename = $event_path.'/'.File::Basename::basename($file); if ( ! $bucket->add_key( $filename, $file_contents ) ) { die "Unable to add key for $filename"; } my $duration = time - $starttime; Debug("PUT to S3 " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec'); } # end foreach file. $moved = 1; }; Error($@) if $@; die $@ if $@; } # end if s3 my $error = ''; if ( ! $moved ) { File::Path::make_path( $NewPath, {error => \my $err} ); if ( @$err ) { for my $diag (@$err) { my ($file, $message) = %$diag; next if $message eq 'File exists'; if ($file eq '') { $error .= "general error: $message\n"; } else { $error .= "problem making $file: $message\n"; } } } if ( $error ) { $ZoneMinder::Database::dbh->commit(); return $error; } my @files = glob("$OldPath/*"); if ( ! @files ) { $ZoneMinder::Database::dbh->commit(); return "No files to move."; } for my $file (@files) { next if $file =~ /^\./; ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint my $starttime = time; Debug("Moving file $file to $NewPath"); my $size = -s $file; if ( ! File::Copy::copy( $file, $NewPath ) ) { $error .= "Copy failed: for $file to $NewPath: $!"; last; } my $duration = time - $starttime; Debug("Copied " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . "/sec"); } # end foreach file. } # end if ! moved if ( $error ) { $ZoneMinder::Database::dbh->commit(); return $error; } # Succeeded in copying all files, so we may now update the Event. $$self{StorageId} = $$NewStorage{Id}; $$self{Storage} = $NewStorage; $error .= $self->save(); if ( $error ) { $ZoneMinder::Database::dbh->commit(); return $error; } Debug("Committing"); $ZoneMinder::Database::dbh->commit(); $self->delete_files( $OldStorage ); Debug("Done deleting files, returning"); return $error; } # end sub MoveTo 1; __END__ =head1 NAME ZoneMinder::Event - Perl Class for events =head1 SYNOPSIS use ZoneMinder::Event; =head1 DESCRIPTION The Event class has everything you need to deal with events from Perl. =head1 AUTHOR Isaac Connor, Eisaac@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2017 ZoneMinder LLC This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/Database.pm0000644000000000000000000001657313365153155023015 0ustar rootroot# ========================================================================== # # ZoneMinder Database Module, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::Database; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter ZoneMinder::Base); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( zmDbConnect zmDbDisconnect zmDbGetMonitors zmDbGetMonitor zmDbGetMonitorAndControl ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Database Access # # ========================================================================== use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Carp; our $dbh = undef; sub zmDbConnect { my $force = shift; if ( $force ) { zmDbDisconnect(); } my $options = shift; if ( ( !defined($dbh) ) or ! $dbh->ping() ) { my ( $host, $portOrSocket ) = ( $ZoneMinder::Config::Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $socket; if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { $socket = ';mysql_socket='.$portOrSocket; } else { $socket = ';host='.$host.';port='.$portOrSocket; } } else { $socket = ';host='.$Config{ZM_DB_HOST}; } my $sslOptions = ''; if ( $Config{ZM_DB_SSL_CA_CERT} ) { $sslOptions = ';'.join(';', 'mysql_ssl=1', 'mysql_ssl_ca_file='.$Config{ZM_DB_SSL_CA_CERT}, 'mysql_ssl_client_key='.$Config{ZM_DB_SSL_CLIENT_KEY}, 'mysql_ssl_client_cert='.$Config{ZM_DB_SSL_CLIENT_CERT} ); } eval { $dbh = DBI->connect( 'DBI:mysql:database='.$Config{ZM_DB_NAME} .$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '') , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ); }; if ( !$dbh or $@ ) { Error("Error reconnecting to db: errstr:$DBI::errstr error val:$@"); } else { $dbh->{AutoCommit} = 1; Fatal('Can\'t set AutoCommit on in database connection') unless $dbh->{AutoCommit}; $dbh->{mysql_auto_reconnect} = 1; Fatal('Can\'t set mysql_auto_reconnect on in database connection') unless $dbh->{mysql_auto_reconnect}; $dbh->trace( 0 ); } # end if success connecting } # end if ! connected return $dbh; } # end sub zmDbConnect sub zmDbDisconnect { if ( defined( $dbh ) ) { $dbh->disconnect(); $dbh = undef; } } use constant DB_MON_ALL => 0; # All monitors use constant DB_MON_CAPT => 1; # All monitors that are capturing use constant DB_MON_ACTIVE => 2; # All monitors that are active use constant DB_MON_MOTION => 3; # All monitors that are doing motion detection use constant DB_MON_RECORD => 4; # All monitors that are doing unconditional recording use constant DB_MON_PASSIVE => 5; # All monitors that are in nodect state sub zmDbGetMonitors { zmDbConnect(); my $function = shift || DB_MON_ALL; my $sql = "select * from Monitors"; if ( $function ) { if ( $function == DB_MON_CAPT ) { $sql .= " where Function >= 'Monitor'"; } elsif ( $function == DB_MON_ACTIVE ) { $sql .= " where Function > 'Monitor'"; } elsif ( $function == DB_MON_MOTION ) { $sql .= " where Function = 'Modect' or Function = 'Mocord'"; } elsif ( $function == DB_MON_RECORD ) { $sql .= " where Function = 'Record' or Function = 'Mocord'"; } elsif ( $function == DB_MON_PASSIVE ) { $sql .= " where Function = 'Nodect'"; } } my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or croak( "Can't execute '$sql': ".$sth->errstr() ); my @monitors; while( my $monitor = $sth->fetchrow_hashref() ) { push( @monitors, $monitor ); } $sth->finish(); return( \@monitors ); } sub zmSQLExecute { my $sql = shift; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( @_ ) or croak( "Can't execute '$sql': ".$sth->errstr() ); return 1; } sub zmDbGetMonitor { zmDbConnect(); my $id = shift; if ( !defined($id) ) { croak("Undefined id in zmDbgetMonitor"); return undef ; } my $sql = 'SELECT * FROM Monitors WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) or croak("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($id) or croak("Can't execute '$sql': ".$sth->errstr()); my $monitor = $sth->fetchrow_hashref(); return $monitor; } sub zmDbGetMonitorAndControl { zmDbConnect(); my $id = shift; return( undef ) if ( !defined($id) ); my $sql = "SELECT C.*,M.*,C.Protocol FROM Monitors as M INNER JOIN Controls as C on (M.ControlId = C.Id) WHERE M.Id = ?" ; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $id ) or Fatal( "Can't execute '$sql': ".$sth->errstr() ); my $monitor = $sth->fetchrow_hashref(); return( $monitor ); } sub start_transaction { #my ( $caller, undef, $line ) = caller; #$openprint::log->debug("Called start_transaction from $caller : $line"); my $d = shift; $d = $dbh if ! $d; my $ac = $d->{AutoCommit}; $d->{AutoCommit} = 0; return $ac; } # end sub start_transaction sub end_transaction { #my ( $caller, undef, $line ) = caller; #$openprint::log->debug("Called end_transaction from $caller : $line"); my ( $d, $ac ) = @_; if ( ! defined $ac ) { Error("Undefined ac"); } $d = $dbh if ! $d; if ( $ac ) { #$log->debug("Committing"); $d->commit(); } # end if $d->{AutoCommit} = $ac; } # end sub end_transaction 1; __END__ =head1 NAME ZoneMinder::Database - Perl module containing database functions used in ZM =head1 SYNOPSIS use ZoneMinder::Database; =head1 DESCRIPTION =head2 EXPORT zmDbConnect zmDbDisconnect zmDbGetMonitors zmDbGetMonitor zmDbGetMonitorAndControl =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =cut ZoneMinder-1.32.2/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in0000644000000000000000000050663213365153155023715 0ustar rootroot# ========================================================================== # # ZoneMinder Config Data Module, $Date: 2011-01-20 18:49:42 +0000 (Thu, 20 Jan 2011) $, $Revision: 3230 $ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== # # This module contains the debug definitions and functions used by the rest # of the ZoneMinder scripts # package ZoneMinder::ConfigData; use 5.006; use strict; use warnings; require Exporter; require ZoneMinder::Base; our @ISA = qw(Exporter ZoneMinder::Base); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use ZoneMinder ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( data => [ qw( %types @options %options_hash ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{data} } ); our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # # Configuration Data # # ========================================================================== use Carp; our $configInitialised = 0; sub INIT { initialiseConfig(); } # Types our %types = ( string => { db_type => 'string', hint => 'string', pattern => qr|^(.+)$|, format => q( $1 ) }, alphanum => { db_type => 'string', hint => 'alphanumeric', pattern => qr|^([a-zA-Z0-9-_]+)$|, format => q( $1 ) }, text => { db_type => 'text', hint => 'free text', pattern => qr|^(.+)$|, format => q( $1 ) }, boolean => { db_type => 'boolean', hint => 'yes|no', pattern => qr|^([yn])|i, check => q( $1 ), format => q( ($1 =~ /^y/) ? 'yes' : 'no' ) }, integer => { db_type => 'integer', hint => 'integer', pattern => qr|^(\d+)$|, format => q( $1 ) }, decimal => { db_type => 'decimal', hint => 'decimal', pattern => qr|^(\d+(?:\.\d+)?)$|, format => q( $1 ) }, hexadecimal => { db_type => 'hexadecimal', hint => 'hexadecimal', pattern => qr|^(?:0x)?([0-9a-f]{1,8})$|, format => q( '0x'.$1 ) }, tristate => { db_type => 'string', hint => 'auto|yes|no', pattern => qr|^([ayn])|i, check=>q( $1 ), format => q( ($1 =~ /^y/) ? 'yes' : ($1 =~ /^n/ ? 'no' : 'auto' ) ) }, abs_path => { db_type => 'string', hint => '/absolute/path/to/somewhere', pattern => qr|^((?:/[^/]*)+?)/?$|, format => q( $1 ) }, rel_path => { db_type => 'string', hint => 'relative/path/to/somewhere', pattern => qr|^((?:[^/].*)?)/?$|, format => q( $1 ) }, directory => { db_type => 'string', hint => 'directory', pattern => qr|^([a-zA-Z0-9-_.]+)$|, format => q( $1 ) }, file => { db_type => 'string', hint => 'filename', pattern => qr|^([a-zA-Z0-9-_.]+)$|, format => q( $1 ) }, hostname => { db_type => 'string', hint => 'host.your.domain', pattern => qr|^([a-zA-Z0-9_.-]+)$|, format => q( $1 ) }, url => { db_type => 'string', hint => 'http://host.your.domain/', pattern => qr|^(?:http://)?(.+)$|, format => q( 'http://'.$1 ) }, email => { db_type => 'string', hint => 'your.name@your.domain', pattern => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, format => q( $1\@$2 ) }, ); our @options = ( { name => 'ZM_SKIN_DEFAULT', default => 'classic', description => 'Default skin used by web interface', help => q` ZoneMinder allows the use of many different web interfaces. This option allows you to set the default skin used by the website. Users can change their skin later, this merely sets the default. `, type => $types{string}, category => 'system', }, { name => 'ZM_CSS_DEFAULT', default => 'classic', description => 'Default set of css files used by web interface', help => q` ZoneMinder allows the use of many different web interfaces, and some skins allow the use of different set of CSS files to control the appearance. This option allows you to set the default set of css files used by the website. Users can change their css later, this merely sets the default. `, type => $types{string}, category => 'system', }, { name => 'ZM_BANDWIDTH_DEFAULT', default => 'high', description => 'Default setting for bandwidth profile used by web interface', help => q`The classic skin for ZoneMinder has different profiles to use for low medium or high bandwidth connections. `, type => $types{string}, category => 'system', }, { name => 'ZM_LANG_DEFAULT', default => 'en_gb', description => 'Default language used by web interface', help => q` ZoneMinder allows the web interface to use languages other than English if the appropriate language file has been created and is present. This option allows you to change the default language that is used from the shipped language, British English, to another language `, type => $types{string}, category => 'system', }, { name => 'ZM_OPT_USE_AUTH', default => 'no', description => 'Authenticate user logins to ZoneMinder', help => q` ZoneMinder can run in two modes. The simplest is an entirely unauthenticated mode where anyone can access ZoneMinder and perform all tasks. This is most suitable for installations where the web server access is limited in other ways. The other mode enables user accounts with varying sets of permissions. Users must login or authenticate to access ZoneMinder and are limited by their defined permissions. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_AUTH_TYPE', default => 'builtin', description => 'What is used to authenticate ZoneMinder users', help => q` ZoneMinder can use two methods to authenticate users when running in authenticated mode. The first is a builtin method where ZoneMinder provides facilities for users to log in and maintains track of their identity. The second method allows interworking with other methods such as http basic authentication which passes an independently authentication 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a user is configured ion ZoneMinder. `, requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' } ], type => { db_type => 'string', hint => 'builtin|remote', pattern => qr|^([br])|i, format => q( $1 =~ /^b/ ? 'builtin' : 'remote' ) }, category => 'system', }, { name => 'ZM_AUTH_RELAY', default => 'hashed', description => 'Method used to relay authentication information', help => q` When ZoneMinder is running in authenticated mode it can pass user details between the web pages and the back end processes. There are two methods for doing this. This first is to use a time limited hashed string which contains no direct username or password details, the second method is to pass the username and passwords around in plaintext. This method is not recommend except where you do not have the md5 libraries available on your system or you have a completely isolated system with no external access. You can also switch off authentication relaying if your system is isolated in other ways. `, requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' } ], type => { db_type => 'string', hint => 'hashed|plain|none', pattern => qr|^([hpn])|i, format => q( ($1 =~ /^h/) ? 'hashed' : ($1 =~ /^p/ ? 'plain' : 'none' ) ) }, category => 'system', }, { name => 'ZM_AUTH_HASH_SECRET', default => '...Change me to something unique...', description => 'Secret for encoding hashed authentication information', help => q` When ZoneMinder is running in hashed authenticated mode it is necessary to generate hashed strings containing encrypted sensitive information such as usernames and password. Although these string are reasonably secure the addition of a random secret increases security substantially. `, requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{string}, category => 'system', }, { name => 'ZM_AUTH_HASH_IPS', default => 'yes', description => 'Include IP addresses in the authentication hash', help => q` When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. `, requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{boolean}, category => 'system', }, { name => 'ZM_AUTH_HASH_TTL', default => '2', description => 'The number of hours that an authentication hash is valid for.', help => q` The default has traditionally been 2 hours. A new hash will automatically be regenerated at half this value. `, requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{integer}, category => 'system', }, { name => 'ZM_AUTH_HASH_LOGINS', default => 'no', description => 'Allow login by authentication hash', help => q` The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for instance from a third party application. If this option is enabled then adding an 'auth' parameter to any request will include a shortcut login bypassing the login screen, if not already logged in. As authentication hashes are time and, optionally, IP limited this can allow short-term access to ZoneMinder screens from other web pages etc. In order to use this the calling application will have to generate the authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to something unique to your system. `, requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{boolean}, category => 'system', }, { name => 'ZM_ENABLE_CSRF_MAGIC', default => 'no', description => 'Enable csrf-magic library', help => q` CSRF stands for Cross-Site Request Forgery which, under specific circumstances, can allow an attacker to perform any task your ZoneMinder user account has permission to perform. To accomplish this, the attacker must write a very specific web page and get you to navigate to it, while you are logged into the ZoneMinder web console at the same time. Enabling ZM_ENABLE_CSRF_MAGIC will help mitigate these kinds of attackes. Be warned this feature is experimental and may cause problems, particularly with the API. If you find a false positive and can document how to reproduce it, then please report it. This feature defaults to OFF currently due to its experimental nature. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_OPT_USE_API', default => 'yes', description => 'Enable ZoneMinder APIs', help => q` ZoneMinder now features a new API using which 3rd party applications can interact with ZoneMinder data. It is STRONGLY recommended that you enable authentication along with APIs. Note that the APIs return sensitive data like Monitor access details which are configured as JSON objects. Which is why we recommend you enabling authentication, especially if you are exposing your ZM instance on the Internet. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_OPT_USE_EVENTNOTIFICATION', default => 'no', description => 'Enable 3rd party Event Notification Server', help => q` zmeventnotification is a 3rd party event notification server that is used to get notifications for alarms detected by ZoneMinder in real time. zmNinja requires this server for push notifications to mobile phones. This option only enables the server if its already installed. Please visit the [zmeventserver project site](https://github.com/pliablepixels/zmeventserver) for installation instructions. `, type => $types{boolean}, category => 'system', }, # Google reCaptcha settings { name => 'ZM_OPT_USE_GOOG_RECAPTCHA', default => 'no', description => 'Add Google reCaptcha to login page', help => q` This option allows you to include a google reCaptcha validation at login. This means in addition to providing a valid usernane and password, you will also have to pass the reCaptcha test. Please note that enabling this option results in the zoneminder login page reach out to google servers for captcha validation. Also please note that enabling this option will break 3rd party clients like zmNinja and zmView as they also need to login to ZoneMinder and they will fail the reCaptcha test. `, requires => [ {name=>'ZM_OPT_USE_AUTH', value=>'yes'} ], type => $types{boolean}, category => 'system', }, { name => 'ZM_OPT_GOOG_RECAPTCHA_SITEKEY', description => 'Your recaptcha site-key', help => q`You need to generate your keys from the Google reCaptcha website. Please refer to the [recaptcha project site](https://www.google.com/recaptcha/) for more details. `, requires => [ {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, category => 'system', }, { name => 'ZM_OPT_GOOG_RECAPTCHA_SECRETKEY', default => '...Insert your recaptcha secret-key here...', description => 'Your recaptcha secret-key', help => q`You need to generate your keys from the Google reCaptcha website. Please refer to the [recaptcha project site](https://www.google.com/recaptcha/) for more details. `, requires => [ {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, category => 'system', }, { name => 'ZM_USE_DEEP_STORAGE', default => 'yes', description => 'Use a deep filesystem hierarchy for events', help => q` This option is now the default for new ZoneMinder systems and should not be changed. Previous versions of ZoneMinder stored all events for a monitor under one folder. Enabling USE_DEEP_STORAGE causes ZoneMinder to store events under a folder structure that follows year/month/day/hour/min/second. Storing events this way avoids the limitation of storing more than 32k files in a single folder inherent in some filesystems. It is important to note that you cannot simply change this option. You must stop zoneminder, enable USE_DEEP_STORAGE, and then run "sudo zmupdate.pl --migrate-events". FAILURE TO DO SO WILL RESULT IN LOSS OF YOUR DATA! Consult the ZoneMinder WiKi for further details. `, type => $types{boolean}, category => 'hidden', }, { name => 'ZM_COLOUR_JPEG_FILES', default => 'no', description => 'Colourise greyscale JPEG files', help => q` Cameras that capture in greyscale can write their captured images to jpeg files with a corresponding greyscale colour space. This saves a small amount of disk space over colour ones. However some tools such as ffmpeg either fail to work with this colour space or have to convert it beforehand. Setting this option to yes uses up a little more space but makes creation of MPEG files much faster. `, type => $types{boolean}, category => 'images', }, { name => 'ZM_ADD_JPEG_COMMENTS', default => 'no', description => 'Add jpeg timestamp annotations as file header comments', help => q` JPEG files may have a number of extra fields added to the file header. The comment field may have any kind of text added. This options allows you to have the same text that is used to annotate the image additionally included as a file header comment. If you archive event images to other locations this may help you locate images for particular events or times if you use software that can read comment headers. `, type => $types{boolean}, category => 'images', }, { name => 'ZM_JPEG_FILE_QUALITY', default => '70', description => 'Set the JPEG quality setting for the saved event files (1-100)', help => q` When ZoneMinder detects an event it will save the images associated with that event to files. These files are in the JPEG format and can be viewed or streamed later. This option specifies what image quality should be used to save these files. A higher number means better quality but less compression so will take up more disk space and take longer to view over a slow connection. By contrast a low number means smaller, quicker to view, files but at the price of lower quality images. This setting applies to all images written except if the capture image has caused an alarm and the alarm file quality option is set at a higher value when that is used instead. `, type => $types{integer}, category => 'images', }, { name => 'ZM_JPEG_ALARM_FILE_QUALITY', default => '0', description => 'Set the JPEG quality setting for the saved event files during an alarm (1-100)', help => q` This value is equivalent to the regular jpeg file quality setting above except that it only applies to images saved while in an alarm state and then only if this value is set to a higher quality setting than the ordinary file setting. If set to a lower value then it is ignored. Thus leaving it at the default of 0 effectively means to use the regular file quality setting for all saved images. This is to prevent acccidentally saving important images at a worse quality setting. `, type => $types{integer}, category => 'images', }, # Deprecated, now stream quality { name => 'ZM_JPEG_IMAGE_QUALITY', default => '70', description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, help => q` When viewing a 'live' stream for a monitor ZoneMinder will grab an image from the buffer and encode it into JPEG format before sending it. This option specifies what image quality should be used to encode these images. A higher number means better quality but less compression so will take longer to view over a slow connection. By contrast a low number means quicker to view images but at the price of lower quality images. This option does not apply when viewing events or still images as these are usually just read from disk and so will be encoded at the quality specified by the previous options. `, type => $types{integer}, category => 'hidden', }, { name => 'ZM_JPEG_STREAM_QUALITY', default => '70', description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, help => q` When viewing a 'live' stream for a monitor ZoneMinder will grab an image from the buffer and encode it into JPEG format before sending it. This option specifies what image quality should be used to encode these images. A higher number means better quality but less compression so will take longer to view over a slow connection. By contrast a low number means quicker to view images but at the price of lower quality images. This option does not apply when viewing events or still images as these are usually just read from disk and so will be encoded at the quality specified by the previous options. `, type => $types{integer}, category => 'images', }, { name => 'ZM_MPEG_TIMED_FRAMES', default => 'yes', description => 'Tag video frames with a timestamp for more realistic streaming', help => q` When using streamed MPEG based video, either for live monitor streams or events, ZoneMinder can send the streams in two ways. If this option is selected then the timestamp for each frame, taken from it's capture time, is included in the stream. This means that where the frame rate varies, for instance around an alarm, the stream will still maintain it's 'real' timing. If this option is not selected then an approximate frame rate is calculated and that is used to schedule frames instead. This option should be selected unless you encounter problems with your preferred streaming method. `, type => $types{boolean}, category => 'images', }, { name => 'ZM_MPEG_LIVE_FORMAT', default => 'swf', description => q`What format 'live' video streams are played in`, help => q` When using MPEG mode ZoneMinder can output live video. However what formats are handled by the browser varies greatly between machines. This option allows you to specify a video format using a file extension format, so you would just enter the extension of the file type you would like and the rest is determined from that. The default of 'asf' works well under Windows with Windows Media Player but I'm currently not sure what, if anything, works on a Linux platform. If you find out please let me know! If this option is left blank then live streams will revert to being in motion jpeg format `, type => $types{string}, category => 'images', }, { name => 'ZM_MPEG_REPLAY_FORMAT', default => 'swf', description => q`What format 'replay' video streams are played in`, help => q` When using MPEG mode ZoneMinder can replay events in encoded video format. However what formats are handled by the browser varies greatly between machines. This option allows you to specify a video format using a file extension format, so you would just enter the extension of the file type you would like and the rest is determined from that. The default of 'asf' works well under Windows with Windows Media Player and 'mpg', or 'avi' etc should work under Linux. If you know any more then please let me know! If this option is left blank then live streams will revert to being in motion jpeg format `, type => $types{string}, category => 'images', }, { name => 'ZM_RAND_STREAM', default => 'yes', description => 'Add a random string to prevent caching of streams', help => q` Some browsers can cache the streams used by ZoneMinder. In order to prevent his a harmless random string can be appended to the url to make each invocation of the stream appear unique. `, type => $types{boolean}, category => 'images', }, { name => 'ZM_OPT_CAMBOZOLA', default => 'no', description => 'Is the (optional) cambozola java streaming client installed', help => q` Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If you use this browser it is highly recommended to install this from the [cambozola project site](http://www.charliemouse.com/code/cambozola/). However, if it is not installed still images at a lower refresh rate can still be viewed. `, type => $types{boolean}, category => 'images', }, { name => 'ZM_PATH_CAMBOZOLA', default => 'cambozola.jar', description => 'Web path to (optional) cambozola java streaming client', help => q` Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If you use this browser it is highly recommended to install this from the [cambozola project site](http://www.charliemouse.com/code/cambozola/). However if it is not installed still images at a lower refresh rate can still be viewed. Leave this as 'cambozola.jar' if cambozola is installed in the same directory as the ZoneMinder web client files. `, requires => [ { name=>'ZM_OPT_CAMBOZOLA', value=>'yes' } ], type => $types{rel_path}, category => 'images', }, { name => 'ZM_RELOAD_CAMBOZOLA', default => '0', description => 'After how many seconds should Cambozola be reloaded in live view', help => q` Cambozola allows for the viewing of streaming MJPEG however it caches the entire stream into cache space on the computer, setting this to a number > 0 will cause it to automatically reload after that many seconds to avoid filling up a hard drive. `, type => $types{integer}, category => 'images', }, { name => 'ZM_TIMESTAMP_ON_CAPTURE', default => 'yes', description => 'Timestamp images as soon as they are captured', help => q` ZoneMinder can add a timestamp to images in two ways. The default method, when this option is set, is that each image is timestamped immediately when captured and so the image held in memory is marked right away. The second method does not timestamp the images until they are either saved as part of an event or accessed over the web. The timestamp used in both methods will contain the same time as this is preserved along with the image. The first method ensures that an image is timestamped regardless of any other circumstances but will result in all images being timestamped even those never saved or viewed. The second method necessitates that saved images are copied before being saved otherwise two timestamps perhaps at different scales may be applied. This has the (perhaps) desirable side effect that the timestamp is always applied at the same resolution so an image that has scaling applied will still have a legible and correctly scaled timestamp. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_TIMESTAMP_CODE_CHAR', default => '%', description => 'Character to used to identify timestamp codes', help => q` There are a few codes one can use to tell ZoneMinder to insert data into the timestamp of each image. Traditionally, the percent (%) character has been used to identify these codes since the current character codes do not conflict with the strftime codes, which can also be used in the timestamp. While this works well for Linux, this does not work well for BSD operating systems. Changing the default character to something else, such as an exclamation point (!), resolves the issue. Note this only affects the timestamp codes built into ZoneMinder. It has no effect on the family of strftime codes one can use. `, type => $types{string}, category => 'config', }, { name => 'ZM_CPU_EXTENSIONS', default => 'yes', description => 'Use advanced CPU extensions to increase performance', help => q` When advanced processor extensions such as SSE2 or SSSE3 are available, ZoneMinder can use them, which should increase performance and reduce system load. Enabling this option on processors that do not support the advanced processors extensions used by ZoneMinder is harmless and will have no effect. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_FAST_IMAGE_BLENDS', default => 'yes', description => 'Use a fast algorithm to blend the reference image', help => q` To detect alarms ZoneMinder needs to blend the captured image with the stored reference image to update it for comparison with the next image. The reference blend percentage specified for the monitor controls how much the new image affects the reference image. There are two methods that are available for this. If this option is set then fast calculation which does not use any multiplication or division is used. This calculation is extremely fast, however it limits the possible blend percentages to 50%, 25%, 12.5%, 6.25%, 3.25% and 1.5%. Any other blend percentage will be rounded to the nearest possible one. The alternative is to switch this option off and use standard blending instead, which is slower. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_OPT_ADAPTIVE_SKIP', default => 'yes', description => 'Should frame analysis try and be efficient in skipping frames', help => q` In previous versions of ZoneMinder the analysis daemon would attempt to keep up with the capture daemon by processing the last captured frame on each pass. This would sometimes have the undesirable side-effect of missing a chunk of the initial activity that caused the alarm because the pre-alarm frames would all have to be written to disk and the database before processing the next frame, leading to some delay between the first and second event frames. Setting this option enables a newer adaptive algorithm where the analysis daemon attempts to process as many captured frames as possible, only skipping frames when in danger of the capture daemon overwriting yet to be processed frames. This skip is variable depending on the size of the ring buffer and the amount of space left in it. Enabling this option will give you much better coverage of the beginning of alarms whilst biasing out any skipped frames towards the middle or end of the event. However you should be aware that this will have the effect of making the analysis daemon run somewhat behind the capture daemon during events and for particularly fast rates of capture it is possible for the adaptive algorithm to be overwhelmed and not have time to react to a rapid build up of pending frames and thus for a buffer overrun condition to occur. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_MAX_SUSPEND_TIME', default => '30', description => 'Maximum time that a monitor may have motion detection suspended', help => q` ZoneMinder allows monitors to have motion detection to be suspended, for instance while panning a camera. Ordinarily this relies on the operator resuming motion detection afterwards as failure to do so can leave a monitor in a permanently suspended state. This setting allows you to set a maximum time which a camera may be suspended for before it automatically resumes motion detection. This time can be extended by subsequent suspend indications after the first so continuous camera movement will also occur while the monitor is suspended. `, type => $types{integer}, category => 'config', }, # Deprecated, really no longer necessary { name => 'ZM_OPT_REMOTE_CAMERAS', default => 'no', description => 'Are you going to use remote/networked cameras', help => q` ZoneMinder can work with both local cameras, ie. those attached physically to your computer and remote or network cameras. If you will be using networked cameras select this option. `, type => $types{boolean}, category => 'hidden', }, # Deprecated, now set on a per monitor basis using the Method field { name => 'ZM_NETCAM_REGEXPS', default => 'yes', description => 'Use regular expression matching with network cameras', help => q` Traditionally ZoneMinder has used complex regular regular expressions to handle the multitude of formats that network cameras produce. In versions from 1.21.1 the default is to use a simpler and faster built in pattern matching methodology. This works well with most networks cameras but if you have problems you can try the older, but more flexible, regular expression based method by selecting this option. Note, to use this method you must have libpcre installed on your system. `, requires => [ { name => 'ZM_OPT_REMOTE_CAMERAS', value => 'yes' } ], type => $types{boolean}, category => 'hidden', }, { name => 'ZM_HTTP_VERSION', default => '1.0', description => 'The version of HTTP that ZoneMinder will use to connect', help => q` ZoneMinder can communicate with network cameras using either of the HTTP/1.1 or HTTP/1.0 standard. A server will normally fall back to the version it supports with no problem so this should usually by left at the default. However it can be changed to HTTP/1.0 if necessary to resolve particular issues. `, type => { db_type => 'string', hint => '1.1|1.0', pattern => qr|^(1\.[01])$|, format => q( $1?$1:'' ) }, category => 'network', }, { name => 'ZM_HTTP_UA', default => 'ZoneMinder', description => 'The user agent that ZoneMinder uses to identify itself', help => q` When ZoneMinder communicates with remote cameras it will identify itself using this string and it's version number. This is normally sufficient, however if a particular cameras expects only to communicate with certain browsers then this can be changed to a different string identifying ZoneMinder as Internet Explorer or Netscape etc. `, type => $types{string}, category => 'network', }, { name => 'ZM_HTTP_TIMEOUT', default => '2500', description => 'How long ZoneMinder waits before giving up on images (milliseconds)', help => q` When retrieving remote images ZoneMinder will wait for this length of time before deciding that an image is not going to arrive and taking steps to retry. This timeout is in milliseconds (1000 per second) and will apply to each part of an image if it is not sent in one whole chunk. `, type => $types{integer}, category => 'network', }, { name => 'ZM_MIN_STREAMING_PORT', default => '', description => 'Alternate port range to contact for streaming video.', help => q` Due to browsers only wanting to open 6 connections, if you have more than 6 monitors, you can have trouble viewing more than 6. This setting specified the beginning of a port range that will be used to contact ZM on. Each monitor will use this value plus the Monitor Id to stream content. So a value of 2000 here will cause a stream for Monitor 1 to hit port 2001. Please ensure that you configure apache appropriately to respond on these ports.`, type => $types{integer}, category => 'network', }, { name => 'ZM_MIN_RTP_PORT', default => '40200', description => 'Minimum port that ZoneMinder will listen for RTP traffic on', help => q` When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This setting specifies the minimum port number that ZoneMinder will use. Ordinarily two adjacent ports are used for each camera, one for control packets and one for data packets. This port should be set to an even number, you may also need to open up a hole in your firewall to allow cameras to connect back if you wish to use unicasting. `, type => $types{integer}, category => 'network', }, { name => 'ZM_MAX_RTP_PORT', default => '40499', description => 'Maximum port that ZoneMinder will listen for RTP traffic on', help => q` When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This setting specifies the maximum port number that ZoneMinder will use. Ordinarily two adjacent ports are used for each camera, one for control packets and one for data packets. This port should be set to an even number, you may also need to open up a hole in your firewall to allow cameras to connect back if you wish to use unicasting. You should also ensure that you have opened up at least two ports for each monitor that will be connecting to unicasting network cameras. `, type => $types{integer}, category => 'network', }, { name => 'ZM_OPT_FFMPEG', default => '@OPT_FFMPEG@', description => 'Is the ffmpeg video encoder/decoder installed', help => q` ZoneMinder can optionally encode a series of video images into an MPEG encoded movie file for viewing, downloading or storage. This option allows you to specify whether you have the ffmpeg tools installed. Note that creating MPEG files can be fairly CPU and disk intensive and is not a required option as events can still be reviewed as video streams without it. `, type => $types{boolean}, category => 'images', }, { name => 'ZM_PATH_FFMPEG', default => '@PATH_FFMPEG@', description => 'Path to (optional) ffmpeg mpeg encoder', help => 'This path should point to where ffmpeg has been installed.', requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{abs_path}, category => 'images', }, { name => 'ZM_FFMPEG_INPUT_OPTIONS', default => '', description => 'Additional input options to ffmpeg', help => q` Ffmpeg can take many options on the command line to control the quality of video produced. This option allows you to specify your own set that apply to the input to ffmpeg (options that are given before the -i option). Check the ffmpeg documentation for a full list of options which may be used here. `, requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, category => 'images', }, { name => 'ZM_FFMPEG_OUTPUT_OPTIONS', default => '-r 25', description => 'Additional output options to ffmpeg', help => q` Ffmpeg can take many options on the command line to control the quality of video produced. This option allows you to specify your own set that apply to the output from ffmpeg (options that are given after the -i option). Check the ffmpeg documentation for a full list of options which may be used here. The most common one will often be to force an output frame rate supported by the video encoder. `, requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, category => 'images', }, { name => 'ZM_FFMPEG_FORMATS', default => 'mpg mpeg wmv asf avi* mov swf 3gp**', description => 'Formats to allow for ffmpeg video generation', help => q` Ffmpeg can generate video in many different formats. This option allows you to list the ones you want to be able to select. As new formats are supported by ffmpeg you can add them here and be able to use them immediately. Adding a '*' after a format indicates that this will be the default format used for web video, adding '**' defines the default format for phone video. `, requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, category => 'images', }, { name => 'ZM_FFMPEG_OPEN_TIMEOUT', default => '10', description => 'Timeout in seconds when opening a stream.', help => q` When Ffmpeg is opening a stream, it can take a long time before failing; certain circumstances even seem to be able to lock indefinitely. This option allows you to set a maximum time in seconds to pass before closing the stream and trying to reopen it again. `, requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{integer}, category => 'images', }, { name => 'ZM_LOG_LEVEL_SYSLOG', default => '0', description => 'Save logging output to the system log', help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output that goes to the system log. ZoneMinder binaries have always logged to the system log but now scripts and web logging is also included. To preserve the previous behaviour you should ensure this value is set to Info or Warning. This option controls the maximum level of logging that will be written, so Info includes Warnings and Errors etc. To disable entirely, set this option to None. You should use caution when setting this option to Debug as it can affect severely affect system performance. If you want debug you will also need to set a level and component below `, type => { db_type => 'integer', hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, category => 'logging', }, { name => 'ZM_LOG_LEVEL_FILE', default => '-5', description => 'Save logging output to component files', help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output that goes to individual log files written by specific components. This is how logging worked previously and although useful for tracking down issues in specific components it also resulted in many disparate log files. To preserve this behaviour you should ensure this value is set to Info or Warning. This option controls the maximum level of logging that will be written, so Info includes Warnings and Errors etc. To disable entirely, set this option to None. You should use caution when setting this option to Debug as it can affect severely affect system performance though file output has less impact than the other options. If you want debug you will also need to set a level and component below `, type => { db_type => 'integer', hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, category => 'logging', }, { name => 'ZM_LOG_LEVEL_WEBLOG', default => '-5', description => 'Save logging output to the weblog', help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output from the web interface that goes to the httpd error log. Note that only web logging from PHP and JavaScript files is included and so this option is really only useful for investigating specific issues with those components. This option controls the maximum level of logging that will be written, so Info includes Warnings and Errors etc. To disable entirely, set this option to None. You should use caution when setting this option to Debug as it can affect severely affect system performance. If you want debug you will also need to set a level and component below `, type => { db_type => 'integer', hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, category => 'logging', }, { name => 'ZM_LOG_LEVEL_DATABASE', default => '0', description => 'Save logging output to the database', help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option lets you control the level of logging output that is written to the database. This is a new option which can make viewing logging output easier and more intuitive and also makes it easier to get an overall impression of how the system is performing. If you have a large or very busy system then it is possible that use of this option may slow your system down if the table becomes very large. Ensure you use the LOG_DATABASE_LIMIT option to keep the table to a manageable size. This option controls the maximum level of logging that will be written, so Info includes Warnings and Errors etc. To disable entirely, set this option to None. You should use caution when setting this option to Debug as it can affect severely affect system performance. If you want debug you will also need to set a level and component below `, type => { db_type => 'integer', hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, category => 'logging', }, { name => 'ZM_LOG_DATABASE_LIMIT', default => '7 day', description => 'Maximum number of log entries to retain', help => q` If you are using database logging then it is possible to quickly build up a large number of entries in the Logs table. This option allows you to specify how many of these entries are kept. If you set this option to a number greater than zero then that number is used to determine the maximum number of rows, less than or equal to zero indicates no limit and is not recommended. You can also set this value to time values such as ' day' which will limit the log entries to those newer than that time. You can specify 'hour', 'day', 'week', 'month' and 'year', note that the values should be singular (no 's' at the end). The Logs table is pruned periodically so it is possible for more than the expected number of rows to be present briefly in the meantime. `, type => $types{string}, category => 'logging', }, { name => 'ZM_LOG_DEBUG', default => 'no', description => 'Switch debugging on', help => q` ZoneMinder components usually support debug logging available to help with diagnosing problems. Binary components have several levels of debug whereas more other components have only one. Normally this is disabled to minimise performance penalties and avoid filling logs too quickly. This option lets you switch on other options that allow you to configure additional debug information to be output. Components will pick up this instruction when they are restarted. `, type => $types{boolean}, category => 'logging', }, { name => 'ZM_LOG_DEBUG_TARGET', default => '', description => 'What components should have extra debug enabled', help => q` There are three scopes of debug available. Leaving this option blank means that all components will use extra debug (not recommended). Setting this option to '_', e.g. _zmc, will limit extra debug to that component only. Setting this option to '__', e.g. '_zmc_m1' will limit extra debug to that instance of the component only. This is ordinarily what you probably want to do. To debug scripts use their names without the .pl extension, e.g. '_zmvideo' and to debug issues with the web interface use '_web'. You can specify multiple targets by separating them with '|' characters. `, requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => $types{string}, category => 'logging', }, { name => 'ZM_LOG_DEBUG_LEVEL', default => 1, description => 'What level of extra debug should be enabled', help => q` There are 9 levels of debug available, with higher numbers being more debug and level 0 being no debug. However not all levels are used by all components. Also if there is debug at a high level it is usually likely to be output at such a volume that it may obstruct normal operation. For this reason you should set the level carefully and cautiously until the degree of debug you wish to see is present. Scripts and the web interface only have one level so this is an on/off type option for them. `, requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => { db_type => 'integer', hint => '1|2|3|4|5|6|7|8|9', pattern => qr|^(\d+)$|, format => q( $1 ) }, category => 'logging', }, { name => 'ZM_LOG_DEBUG_FILE', default => '@ZM_LOGDIR@/zm_debug.log+', description => 'Where extra debug is output to', help => q` This option allows you to specify a different target for debug output. All components have a default log file which will norally be in /tmp or /var/log and this is where debug will be written to if this value is empty. Adding a path here will temporarily redirect debug, and other logging output, to this file. This option is a simple filename and you are debugging several components then they will all try and write to the same file with undesirable consequences. Appending a '+' to the filename will cause the file to be created with a '.' suffix containing your process id. In this way debug from each run of a component is kept separate. This is the recommended setting as it will also prevent subsequent runs from overwriting the same log. You should ensure that permissions are set up to allow writing to the file and directory specified here. `, requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => $types{string}, category => 'logging', }, { name => 'ZM_LOG_CHECK_PERIOD', default => '900', description => 'Time period used when calculating overall system health', help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to indicate what period of historical events are used in this calculation. This value is expressed in seconds and is ignored if LOG_LEVEL_DATABASE is set to None. `, type => $types{integer}, category => 'logging', }, { name => 'ZM_LOG_ALERT_WAR_COUNT', default => '1', description => 'Number of warnings indicating system alert state', help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to specify how many warnings must have occurred within the defined time period to generate an overall system alert state. A value of zero means warnings are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. `, type => $types{integer}, category => 'logging', }, { name => 'ZM_LOG_ALERT_ERR_COUNT', default => '1', description => 'Number of errors indicating system alert state', help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to specify how many errors must have occurred within the defined time period to generate an overall system alert state. A value of zero means errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. `, type => $types{integer}, category => 'logging', }, { name => 'ZM_LOG_ALERT_FAT_COUNT', default => '0', description => 'Number of fatal error indicating system alert state', help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to specify how many fatal errors (including panics) must have occurred within the defined time period to generate an overall system alert state. A value of zero means fatal errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. `, type => $types{integer}, category => 'logging', }, { name => 'ZM_LOG_ALARM_WAR_COUNT', default => '100', description => 'Number of warnings indicating system alarm state', help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to specify how many warnings must have occurred within the defined time period to generate an overall system alarm state. A value of zero means warnings are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. `, type => $types{integer}, category => 'logging', }, { name => 'ZM_LOG_ALARM_ERR_COUNT', default => '10', description => 'Number of errors indicating system alarm state', help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to specify how many errors must have occurred within the defined time period to generate an overall system alarm state. A value of zero means errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. `, type => $types{integer}, category => 'logging', }, { name => 'ZM_LOG_ALARM_FAT_COUNT', default => '1', description => 'Number of fatal error indicating system alarm state', help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to specify how many fatal errors (including panics) must have occurred within the defined time period to generate an overall system alarm state. A value of zero means fatal errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. `, type => $types{integer}, category => 'logging', }, { name => 'ZM_RECORD_EVENT_STATS', default => 'yes', description => 'Record event statistical information, switch off if too slow', help => q` This version of ZoneMinder records detailed information about events in the Stats table. This can help in profiling what the optimum settings are for Zones though this is tricky at present. However in future releases this will be done more easily and intuitively, especially with a large sample of events. The default option of 'yes' allows this information to be collected now in readiness for this but if you are concerned about performance you can switch this off in which case no Stats information will be saved. `, type => $types{boolean}, category => 'logging', }, { name => 'ZM_RECORD_DIAG_IMAGES', default => 'no', description => 'Record intermediate alarm diagnostic images, can be very slow', help => q` In addition to recording event statistics you can also record the intermediate diagnostic images that display the results of the various checks and processing that occur when trying to determine if an alarm event has taken place. There are several of these images generated for each frame and zone for each alarm or alert frame so this can have a massive impact on performance. Only switch this setting on for debug or analysis purposes and remember to switch it off again once no longer required. `, type => $types{boolean}, category => 'logging', }, { name => 'ZM_DUMP_CORES', default => 'no', description => 'Create core files on unexpected process failure.', help => q` When an unrecoverable error occurs in a ZoneMinder binary process is has traditionally been trapped and the details written to logs to aid in remote analysis. However in some cases it is easier to diagnose the error if a core file, which is a memory dump of the process at the time of the error, is created. This can be interactively analysed in the debugger and may reveal more or better information than that available from the logs. This option is recommended for advanced users only otherwise leave at the default. Note using this option to trigger core files will mean that there will be no indication in the binary logs that a process has died, they will just stop, however the zmdc log will still contain an entry. Also note that you may have to explicitly enable core file creation on your system via the 'ulimit -c' command or other means otherwise no file will be created regardless of the value of this option. `, type => $types{boolean}, category => 'logging', }, { name => 'ZM_WEB_TITLE', default => 'ZoneMinder', description => 'The title displayed wherever the site references itself.', help => q` If you want the site to identify as something other than ZoneMinder, change this here. It can be used to more accurately identify this installation from others. `, type => $types{string}, category => 'web', }, { name => 'ZM_WEB_TITLE_PREFIX', default => 'ZM', description => 'The title prefix displayed on each window', help => q` If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification. `, type => $types{string}, category => 'web', }, { name => 'ZM_HOME_URL', default => 'http://zoneminder.com', description => 'The url used in the home/logo area of the navigation bar.', help => q` By default this takes you to the zoneminder.com website, but perhaps you would prefer it to take you somewhere else. `, type => $types{string}, category => 'web', }, { name => 'ZM_HOME_CONTENT', default => 'ZoneMinder', description => 'The content of the home button.', help => q` You may wish to set this to empty if you are using css to put a background image on it. `, type => $types{string}, category => 'web', }, { name => 'ZM_WEB_CONSOLE_BANNER', default => '', description => 'Arbitrary text message near the top of the console', help => q` Allows the administrator to place an arbitrary text message near the top of the web console. This is useful for the developers to display a message which indicates the running instance of ZoneMinder is a development snapshot, but it can also be used for any other purpose as well. `, type => $types{string}, category => 'web', }, { name => 'ZM_WEB_EVENT_DISK_SPACE', default => 'no', description => 'Whether to show disk space used by each event.', help => q`Adds another column to the listing of events showing the disk space used by the event. This will impart a small overhead as it will call du on the event directory. In practice this overhead is fairly small but may be noticeable on IO-constrained systems. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_WEB_RESIZE_CONSOLE', default => 'yes', description => 'Should the console window resize itself to fit', help => q` Traditionally the main ZoneMinder web console window has resized itself to shrink to a size small enough to list only the monitors that are actually present. This is intended to make the window more unobtrusize but may not be to everyones tastes, especially if opened in a tab in browsers which support this kind if layout. Switch this option off to have the console window size left to the users preference `, type => $types{boolean}, category => 'web', }, { name => 'ZM_WEB_ID_ON_CONSOLE', default => 'no', description => 'Should the console list the monitor id', help => q` Some find it useful to have the id always visible on the console. This option will add a column listing it. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_WEB_POPUP_ON_ALARM', default => 'yes', description => 'Should the monitor window jump to the top if an alarm occurs', help => q` When viewing a live monitor stream you can specify whether you want the window to pop to the front if an alarm occurs when the window is minimised or behind another window. This is most useful if your monitors are over doors for example when they can pop up if someone comes to the doorway. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_OPT_X10', default => 'no', description => 'Support interfacing with X10 devices', help => q` If you have an X10 Home Automation setup in your home you can use ZoneMinder to initiate or react to X10 signals if your computer has the appropriate interface controller. This option indicates whether X10 options will be available in the browser client. `, type => $types{boolean}, category => 'x10', }, { name => 'ZM_X10_DEVICE', default => '/dev/ttyS0', description => 'What device is your X10 controller connected on', requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], help => q` If you have an X10 controller device (e.g. XM10U) connected to your computer this option details which port it is connected on, the default of /dev/ttyS0 maps to serial or com port 1. `, type => $types{abs_path}, category => 'x10', }, { name => 'ZM_X10_HOUSE_CODE', default => 'A', description => 'What X10 house code should be used', requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], help => q` X10 devices are grouped together by identifying them as all belonging to one House Code. This option details what that is. It should be a single letter between A and P. `, type => { db_type=>'string', hint=>'A-P', pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, category => 'x10', }, { name => 'ZM_X10_DB_RELOAD_INTERVAL', default => '60', description => 'How often (in seconds) the X10 daemon reloads the monitors from the database', requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], help => q` The zmx10 daemon periodically checks the database to find out what X10 events trigger, or result from, alarms. This option determines how frequently this check occurs, unless you change this area frequently this can be a fairly large value. `, type => $types{integer}, category => 'x10', }, { name => 'ZM_WEB_SOUND_ON_ALARM', default => 'no', description => 'Should the monitor window play a sound if an alarm occurs', help => q` When viewing a live monitor stream you can specify whether you want the window to play a sound to alert you if an alarm occurs. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_WEB_ALARM_SOUND', default => '', description => 'The sound to play on alarm, put this in the sounds directory', help => q` You can specify a sound file to play if an alarm occurs whilst you are watching a live monitor stream. So long as your browser understands the format it does not need to be any particular type. This file should be placed in the sounds directory defined earlier. `, type => $types{file}, requires => [ { name => 'ZM_WEB_SOUND_ON_ALARM', value => 'yes' } ], category => 'web', }, { name => 'ZM_WEB_COMPACT_MONTAGE', default => 'no', description => 'Compact the montage view by removing extra detail', help => q` The montage view shows the output of all of your active monitors in one window. This include a small menu and status information for each one. This can increase the web traffic and make the window larger than may be desired. Setting this option on removes all this extraneous information and just displays the images. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_OPT_FAST_DELETE', default => 'no', description => 'Delete only event database records for speed', help => q` Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. It is recommended that you set this option which means that the browser client only deletes the key entries in the events table, which means the events will no longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later. Note that this feature is less relevant with modern hardware. Recommend this feature be left off. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_STRICT_VIDEO_CONFIG', default => 'yes', description => 'Allow errors in setting video config to be fatal', help => q` With some video devices errors can be reported in setting the various video attributes when in fact the operation was successful. Switching this option off will still allow these errors to be reported but will not cause them to kill the video capture daemon. Note however that doing this will cause all errors to be ignored including those which are genuine and which may cause the video capture to not function correctly. Use this option with caution. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_LD_PRELOAD', default => '', description => "Path to library to preload before launching daemons", help => q` Some older cameras require the use of the v4l1 compat library. This setting allows the setting of the path to the library, so that it can be loaded by zmdc.pl before launching zmc. `, type => $types{abs_path}, category => 'config', }, { name => 'ZM_V4L_MULTI_BUFFER', default => 'yes', description => 'Use more than one buffer for Video 4 Linux devices', help => q` Performance when using Video 4 Linux devices is usually best if multiple buffers are used allowing the next image to be captured while the previous one is being processed. If you have multiple devices on a card sharing one input that requires switching then this approach can sometimes cause frames from one source to be mixed up with frames from another. Switching this option off prevents multi buffering resulting in slower but more stable image capture. This option is ignored for non-local cameras or if only one input is present on a capture chip. This option addresses a similar problem to the ZM_CAPTURES_PER_FRAME option and you should normally change the value of only one of the options at a time. If you have different capture cards that need different values you can override them in each individual monitor on the source page. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_CAPTURES_PER_FRAME', default => '1', description => 'How many images are captured per returned frame, for shared local cameras', help => q` If you are using cameras attached to a video capture card which forces multiple inputs to share one capture chip, it can sometimes produce images with interlaced frames reversed resulting in poor image quality and a distinctive comb edge appearance. Increasing this setting allows you to force additional image captures before one is selected as the captured frame. This allows the capture hardware to 'settle down' and produce better quality images at the price of lesser capture rates. This option has no effect on (a) network cameras, or (b) where multiple inputs do not share a capture chip. This option addresses a similar problem to the ZM_V4L_MULTI_BUFFER option and you should normally change the value of only one of the options at a time. If you have different capture cards that need different values you can override them in each individual monitor on the source page. `, type => $types{integer}, category => 'config', }, { name => 'ZM_FILTER_RELOAD_DELAY', default => '300', description => 'How often (in seconds) filters are reloaded in zmfilter', help => q` ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often the filters are reloaded from the database to get the latest versions or new filters. If you don't change filters very often this value can be set to a large value. `, type => $types{integer}, category => 'system', }, { name => 'ZM_FILTER_EXECUTE_INTERVAL', default => '60', description => 'How often (in seconds) to run automatic saved filters', help => q` ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often the filters are executed on the saved event in the database. If you want a rapid response to new events this should be a smaller value, however this may increase the overall load on the system and affect performance of other elements. `, type => $types{integer}, category => 'system', }, { name => 'ZM_OPT_UPLOAD', default => 'no', description => 'Should ZoneMinder support uploading events from filters', help => q` In ZoneMinder you can create event filters that specify whether events that match certain criteria should be uploaded to a remote server for archiving. This option specifies whether this functionality should be available `, type => $types{boolean}, category => 'upload', }, { name => 'ZM_UPLOAD_ARCH_FORMAT', default => 'tar', description => 'What format the uploaded events should be created in.', requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], help => q` Uploaded events may be stored in either .tar or .zip format, this option specifies which. Note that to use this you will need to have the Archive::Tar and/or Archive::Zip perl modules installed. `, type => { db_type =>'string', hint =>'tar|zip', pattern =>qr|^([tz])|i, format =>q( $1 =~ /^t/ ? 'tar' : 'zip' ) }, category => 'upload', }, { name => 'ZM_UPLOAD_ARCH_COMPRESS', default => 'no', description => 'Should archive files be compressed', help => q` When the archive files are created they can be compressed. However in general since the images are compressed already this saves only a minimal amount of space versus utilising more CPU in their creation. Only enable if you have CPU to waste and are limited in disk space on your remote server or bandwidth. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, category => 'upload', }, { name => 'ZM_UPLOAD_ARCH_ANALYSE', default => 'no', description => 'Include the analysis files in the archive', help => q` When the archive files are created they can contain either just the captured frames or both the captured frames and, for frames that caused an alarm, the analysed image with the changed area highlighted. This option controls files are included. Only include analysed frames if you have a high bandwidth connection to the remote server or if you need help in figuring out what caused an alarm in the first place as archives with these files in can be considerably larger. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, category => 'upload', }, { name => 'ZM_UPLOAD_PROTOCOL', default => 'ftp', description => 'What protocol to use to upload events', requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], help => q` ZoneMinder can upload events to a remote server using either FTP or SFTP. Regular FTP is widely supported but not necessarily very secure whereas SFTP (Secure FTP) runs over an ssh connection and so is encrypted and uses regular ssh ports. Note that to use this you will need to have the appropriate perl module, either Net::FTP or Net::SFTP installed depending on your choice. `, type => { db_type =>'string', hint =>'ftp|sftp', pattern =>qr|^([tz])|i, format =>q( $1 =~ /^f/ ? 'ftp' : 'sftp' ) }, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_HOST', default => '', description => 'The remote server to upload to', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the name, or ip address, of the server to use. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{hostname}, category => 'hidden', }, { name => 'ZM_UPLOAD_HOST', default => '', description => 'The remote server to upload events to', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the name, or ip address, of the server to use. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{hostname}, category => 'upload', }, { name => 'ZM_UPLOAD_PORT', default => '', description => 'The port on the remote upload server, if not the default (SFTP only)', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. If you are using the SFTP protocol then this option allows you to specify a particular port to use for connection. If this option is left blank then the default, port 22, is used. This option is ignored for FTP uploads. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_USER', default => '', description => 'Your ftp username', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the username that ZoneMinder should use to log in for ftp transfer. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{alphanum}, category => 'hidden', }, { name => 'ZM_UPLOAD_USER', default => '', description => 'Remote server username', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the username that ZoneMinder should use to log in for transfer. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{alphanum}, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_PASS', default => '', description => 'Your ftp password', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the password that ZoneMinder should use to log in for ftp transfer. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{string}, category => 'hidden', }, { name => 'ZM_UPLOAD_PASS', default => '', description => 'Remote server password', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the password that ZoneMinder should use to log in for transfer. If you are using certificate based logins for SFTP servers you can leave this option blank. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{string}, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_LOC_DIR', default => '@ZM_TMPDIR@', description => 'The local directory in which to create upload files', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{abs_path}, category => 'hidden', }, { name => 'ZM_UPLOAD_LOC_DIR', default => '@ZM_TMPDIR@', description => 'The local directory in which to create upload files', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{abs_path}, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_REM_DIR', default => '', description => 'The remote directory to upload to', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the remote directory that ZoneMinder should use to upload event files to. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{rel_path}, category => 'hidden', }, { name => 'ZM_UPLOAD_REM_DIR', default => '', description => 'The remote directory to upload to', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the remote directory that ZoneMinder should use to upload event files to. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{rel_path}, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_TIMEOUT', default => '120', description => 'How long to allow the transfer to take for each file', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the maximum ftp inactivity timeout (in seconds) that should be tolerated before ZoneMinder determines that the transfer has failed and closes down the connection. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, category => 'hidden', }, { name => 'ZM_UPLOAD_TIMEOUT', default => '120', description => 'How long to allow the transfer to take for each file', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the maximum inactivity timeout (in seconds) that should be tolerated before ZoneMinder determines that the transfer has failed and closes down the connection. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, category => 'upload', }, { name => 'ZM_UPLOAD_STRICT', default => 'no', description => 'Require strict host key checking for SFTP uploads', help => q` You can require SFTP uploads to verify the host key of the remote server for protection against man-in-the-middle attacks. You will need to add the server's key to the known_hosts file. On most systems, this will be ~/.ssh/known_hosts, where ~ is the home directory of the web server running ZoneMinder. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_PASSIVE', default => 'yes', description => 'Use passive ftp when uploading', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates that ftp transfers should be done in passive mode. This uses a single connection for all ftp activity and, whilst slower than active transfers, is more robust and likely to work from behind filewalls. This option is ignored for SFTP transfers. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], help => q` If your computer is behind a firewall or proxy you may need to set FTP to passive mode. In fact for simple transfers it makes little sense to do otherwise anyway but you can set this to 'No' if you wish. `, type => $types{boolean}, category => 'upload', }, { name => 'ZM_UPLOAD_FTP_DEBUG', default => 'no', description => 'Switch ftp debugging on', help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. If you are having (or expecting) troubles with uploading events then setting this to 'yes' permits additional information to be included in the zmfilter log file. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, category => 'hidden', }, { name => 'ZM_UPLOAD_DEBUG', default => 'no', description => 'Switch upload debugging on', help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. If you are having (or expecting) troubles with uploading events then setting this to 'yes' permits additional information to be generated by the underlying transfer modules and included in the logs. `, requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, category => 'upload', }, { name => 'ZM_OPT_EMAIL', default => 'no', description => 'Should ZoneMinder email you details of events that match corresponding filters', help => q` In ZoneMinder you can create event filters that specify whether events that match certain criteria should have their details emailed to you at a designated email address. This will allow you to be notified of events as soon as they occur and also to quickly view the events directly. This option specifies whether this functionality should be available. The email created with this option can be any size and is intended to be sent to a regular email reader rather than a mobile device. `, type => $types{boolean}, category => 'mail', }, { name => 'ZM_EMAIL_ADDRESS', default => '', description => 'The email address to send matching event details to', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], help => q` This option is used to define the email address that any events that match the appropriate filters will be sent to. `, type => $types{email}, category => 'mail', }, { name => 'ZM_EMAIL_TEXT', default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" body = " Hello, An alarm has been detected on your installation of the ZoneMinder. The details are as follows :- Monitor : %MN% Event Id : %EI% Length : %EL% Frames : %EF% (%EFA%) Scores : t%EST% m%ESM% a%ESA% This alarm was matched by the %FN% filter and can be viewed at %EPS% ZoneMinder"', description => 'The text of the email used to send matching event details', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], help => q` This option is used to define the content of the email that is sent for any events that match the appropriate filters. `, type => $types{text}, category => 'hidden', }, { name => 'ZM_EMAIL_SUBJECT', default => 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', description => 'The subject of the email used to send matching event details', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], help => q` This option is used to define the subject of the email that is sent for any events that match the appropriate filters. `, type => $types{string}, category => 'mail', }, { name => 'ZM_EMAIL_BODY', default => ' Hello, An alarm has been detected on your installation of the ZoneMinder. The details are as follows :- Monitor : %MN% Event Id : %EI% Length : %EL% Frames : %EF% (%EFA%) Scores : t%EST% m%ESM% a%ESA% This alarm was matched by the %FN% filter and can be viewed at %EPS% ZoneMinder', description => 'The body of the email used to send matching event details', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], help => q` This option is used to define the content of the email that is sent for any events that match the appropriate filters. `, type => $types{text}, category => 'mail', }, { name => 'ZM_OPT_MESSAGE', default => 'no', description => 'Should ZoneMinder message you with details of events that match corresponding filters', help => q` In ZoneMinder you can create event filters that specify whether events that match certain criteria should have their details sent to you at a designated short message email address. This will allow you to be notified of events as soon as they occur. This option specifies whether this functionality should be available. The email created by this option will be brief and is intended to be sent to an SMS gateway or a minimal mail reader such as a mobile device or phone rather than a regular email reader. `, type => $types{boolean}, category => 'mail', }, { name => 'ZM_MESSAGE_ADDRESS', default => '', description => 'The email address to send matching event details to', requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` This option is used to define the short message email address that any events that match the appropriate filters will be sent to. `, type => $types{email}, category => 'mail', }, { name => 'ZM_MESSAGE_TEXT', default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', description => 'The text of the message used to send matching event details', requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` This option is used to define the content of the message that is sent for any events that match the appropriate filters. `, type => $types{text}, category => 'hidden', }, { name => 'ZM_MESSAGE_SUBJECT', default => 'ZoneMinder: Alarm - %MN%-%EI%', description => 'The subject of the message used to send matching event details', requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` This option is used to define the subject of the message that is sent for any events that match the appropriate filters. `, type => $types{string}, category => 'mail', }, { name => 'ZM_MESSAGE_BODY', default => 'ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', description => 'The body of the message used to send matching event details', requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` This option is used to define the content of the message that is sent for any events that match the appropriate filters. `, type => $types{text}, category => 'mail', }, { name => 'ZM_NEW_MAIL_MODULES', default => 'no', description => 'Use a newer perl method to send emails', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' }, { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` Traditionally ZoneMinder has used the MIME::Entity perl module to construct and send notification emails and messages. Some people have reported problems with this module not being present at all or flexible enough for their needs. If you are one of those people this option allows you to select a new mailing method using MIME::Lite and Net::SMTP instead. This method was contributed by Ross Melin and should work for everyone but has not been extensively tested so currently is not selected by default. `, type => $types{boolean}, category => 'mail', }, { name => 'ZM_EMAIL_HOST', default => 'localhost', description => 'The host address of your SMTP mail server', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' }, { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` If you have chosen SMTP as the method by which to send notification emails or messages then this option allows you to choose which SMTP server to use to send them. The default of localhost may work if you have the sendmail, exim or a similar daemon running however you may wish to enter your ISP's SMTP mail server here. `, type => $types{hostname}, category => 'mail', }, { name => 'ZM_FROM_EMAIL', default => '', description => 'The email address you wish your event notifications to originate from', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' }, { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` The emails or messages that will be sent to you informing you of events can appear to come from a designated email address to help you with mail filtering etc. An address of something like ZoneMinder\@your.domain is recommended. `, type => $types{email}, category => 'mail', }, { name => 'ZM_URL', default => '', description => 'The URL of your ZoneMinder installation', requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' }, { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], help => q` The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. http://host.your.domain/zm.php. `, type => $types{url}, category => 'mail', }, { name => 'ZM_MAX_RESTART_DELAY', default => '600', description => 'Maximum delay (in seconds) for daemon restart attempts.', help => q` The zmdc (zm daemon control) process controls when processeses are started or stopped and will attempt to restart any that fail. If a daemon fails frequently then a delay is introduced between each restart attempt. If the daemon stills fails then this delay is increased to prevent extra load being placed on the system by continual restarts. This option controls what this maximum delay is. `, type => $types{integer}, category => 'system', }, { name => 'ZM_STATS_UPDATE_INTERVAL', default => '60', description => 'How often to update the database statistics', help => q` The zmstats daemon performs various db queries that may take a long time in the background. `, type => $types{integer}, category => 'system', }, { name => 'ZM_WATCH_CHECK_INTERVAL', default => '10', description => 'How often to check the capture daemons have not locked up', help => q` The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines how often the daemons are checked. `, type => $types{integer}, category => 'system', }, { name => 'ZM_WATCH_MAX_DELAY', default => '5', description => 'The maximum delay allowed since the last captured image', help => q` The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines the maximum delay to allow since the last captured frame. The daemon will be restarted if it has not captured any images after this period though the actual restart may take slightly longer in conjunction with the check interval value above. `, type => $types{decimal}, category => 'system', }, { name => 'ZM_RUN_AUDIT', default => 'yes', description => 'Run zmaudit to check data consistency', help => q` The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronise the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. This is recommended for most systems however if you have a very large number of events the process of scanning the database and filesystem may take a long time and impact performance. In this case you may prefer to not have zmaudit running unconditionally and schedule occasional checks at other, more convenient, times. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_AUDIT_CHECK_INTERVAL', default => '900', description => 'How often to check database and filesystem consistency', help => q` The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronise the two data stores. The default check interval of 900 seconds (15 minutes) is fine for most systems however if you have a very large number of events the process of scanning the database and filesystem may take a long time and impact performance. In this case you may prefer to make this interval much larger to reduce the impact on your system. This option determines how often these checks are performed. `, type => $types{integer}, category => 'system', }, { name => 'ZM_AUDIT_MIN_AGE', default => '86400', description => 'The minimum age in seconds event data must be in order to be deleted.', help => q` The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. Event files or db records that are younger than this setting will not be deleted and a warning will be given. `, type => $types{integer}, category => 'system', }, { name => 'ZM_FORCED_ALARM_SCORE', default => '255', description => 'Score to give forced alarms', help => q` The 'zmu' utility can be used to force an alarm on a monitor rather than rely on the motion detection algorithms. This option determines what score to give these alarms to distinguish them from regular ones. It must be 255 or less. `, type => $types{integer}, category => 'config', }, { name => 'ZM_BULK_FRAME_INTERVAL', default => '100', description => 'How often a bulk frame should be written to the database', help => q` Traditionally ZoneMinder writes an entry into the Frames database table for each frame that is captured and saved. This works well in motion detection scenarios but when in a DVR situation ('Record' or 'Mocord' mode) this results in a huge number of frame writes and a lot of database and disk bandwidth for very little additional information. Setting this to a non-zero value will enabled ZoneMinder to group these non-alarm frames into one 'bulk' frame entry which saves a lot of bandwidth and space. The only disadvantage of this is that timing information for individual frames is lost but in constant frame rate situations this is usually not significant. This setting is ignored in Modect mode and individual frames are still written if an alarm occurs in Mocord mode also. `, type => $types{integer}, category => 'config', }, { name => 'ZM_EVENT_CLOSE_MODE', default => 'idle', description => 'When continuous events are closed.', help => q` When a monitor is running in a continuous recording mode (Record or Mocord) events are usually closed after a fixed period of time (the section length). However in Mocord mode it is possible that motion detection may occur near the end of a section. This option controls what happens when an alarm occurs in Mocord mode. The 'time' setting means that the event will be closed at the end of the section regardless of alarm activity. The 'idle' setting means that the event will be closed at the end of the section if there is no alarm activity occurring at the time otherwise it will be closed once the alarm is over meaning the event may end up being longer than the normal section length. The 'alarm' setting means that if an alarm occurs during the event, the event will be closed once the alarm is over regardless of when this occurs. This has the effect of limiting the number of alarms to one per event and the events will be shorter than the section length if an alarm has occurred. `, type => $types{boolean}, type => { db_type =>'string', hint =>'time|idle|alarm', pattern =>qr|^([tia])|i, format =>q( ($1 =~ /^t/) ? 'time' : ($1 =~ /^i/ ? 'idle' : 'time' ) ) }, category => 'config', }, # Deprecated, superseded by event close mode { name => 'ZM_FORCE_CLOSE_EVENTS', default => 'no', description => 'Close events at section ends.', help => q` When a monitor is running in a continuous recording mode (Record or Mocord) events are usually closed after a fixed period of time (the section length). However in Mocord mode it is possible that motion detection may occur near the end of a section and ordinarily this will prevent the event being closed until the motion has ceased. Switching this option on will force the event closed at the specified time regardless of any motion activity. `, type => $types{boolean}, category => 'hidden', }, { name => 'ZM_CREATE_ANALYSIS_IMAGES', default => 'yes', description => 'Create analysed alarm images with motion outlined', help => q` By default during an alarm ZoneMinder records both the raw captured image and one that has been analysed and had areas where motion was detected outlined. This can be very useful during zone configuration or in analysing why events occurred. However it also incurs some overhead and in a stable system may no longer be necessary. This parameter allows you to switch the generation of these images off. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_WEIGHTED_ALARM_CENTRES', default => 'no', description => 'Use a weighted algorithm to calculate the centre of an alarm', help => q` ZoneMinder will always calculate the centre point of an alarm in a zone to give some indication of where on the screen it is. This can be used by the experimental motion tracking feature or your own custom extensions. In the alarmed or filtered pixels mode this is a simple midpoint between the extents of the detected pixels. However in the blob method this can instead be calculated using weighted pixel locations to give more accurate positioning for irregularly shaped blobs. This method, while more precise is also slower and so is turned off by default. `, type => $types{boolean}, category => 'config', }, { name => 'ZM_EVENT_IMAGE_DIGITS', default => '5', description => 'How many significant digits are used in event image numbering', help => q` As event images are captured they are stored to the filesystem with a numerical index. By default this index has three digits so the numbers start 001, 002 etc. This works works for most scenarios as events with more than 999 frames are rarely captured. However if you have extremely long events and use external applications then you may wish to increase this to ensure correct sorting of images in listings etc. Warning, increasing this value on a live system may render existing events unviewable as the event will have been saved with the previous scheme. Decreasing this value should have no ill effects. `, type => $types{integer}, category => 'config', }, { name => 'ZM_DEFAULT_ASPECT_RATIO', default => '4:3', description => 'The default width:height aspect ratio used in monitors', help => q` When specifying the dimensions of monitors you can click a checkbox to ensure that the width stays in the correct ratio to the height, or vice versa. This setting allows you to indicate what the ratio of these settings should be. This should be specified in the format : and the default of 4:3 normally be acceptable but 11:9 is another common setting. If the checkbox is not clicked when specifying monitor dimensions this setting has no effect. `, type => $types{string}, category => 'config', }, { name => 'ZM_USER_SELF_EDIT', default => 'no', description => 'Allow unprivileged users to change their details', help => q` Ordinarily only users with system edit privilege are able to change users details. Switching this option on allows ordinary users to change their passwords and their language settings `, type => $types{boolean}, category => 'config', }, { name => 'ZM_OPT_CONTROL', default => 'no', description => 'Support controllable (e.g. PTZ) cameras', help => q` ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_OPT_TRIGGERS', default => 'no', description => 'Interface external event triggers via socket or device files', help => q` ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_CHECK_FOR_UPDATES', default => 'yes', description => 'Check with zoneminder.com for updated versions', help => q` From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable `, type => $types{boolean}, category => 'system', }, { name => 'ZM_TELEMETRY_DATA', default => 'no', description => 'Send usage information to ZoneMinder', help => q` Enable collection of usage information of the local system and send it to the ZoneMinder development team. This data will be used to determine things like who and where our customers are, how big their systems are, the underlying hardware and operating system, etc. This is being done for the sole purpoase of creating a better product for our target audience. This script is intended to be completely transparent to the end user, and can be disabled from the web console under Options. For more details on what information we collect, please refer to our [privacy](?view=privacy) statement. `, type => $types{boolean}, category => 'system', }, { name => 'ZM_TELEMETRY_UUID', default => '', description => 'Unique identifier for ZoneMinder telemetry', help => q` This variable is auto-generated once by the system and is used to uniquely identify it among all other ZoneMinder systems in existence. `, type => $types{string}, category => 'dynamic', }, { name => 'ZM_TELEMETRY_LAST_UPLOAD', default => '', description => 'When the last ZoneMinder telemetry upload ocurred', help => '', type => $types{integer}, readonly => 1, category => 'dynamic', }, { name => 'ZM_TELEMETRY_INTERVAL', default => '14*24*60*60', description => 'Interval in seconds between telemetry updates.', help => 'This value can be expressed as a mathematical expression for ease.', type => $types{string}, category => 'system', }, { name => 'ZM_TELEMETRY_SERVER_ENDPOINT', default => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5', description => 'URL that ZoneMinder will send usage data to', help => '', type => $types{url}, category => 'hidden', }, { name => 'ZM_UPDATE_CHECK_PROXY', default => '', description => 'Proxy url if required to access zoneminder.com', help => q` If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of http://:/ `, type => $types{string}, category => 'system', }, { name => 'ZM_SHM_KEY', default => '0x7a6d0000', description => 'Shared memory root key to use', help => q` ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored. `, type => $types{hexadecimal}, category => 'system', }, # Deprecated, really no longer necessary { name => 'ZM_WEB_REFRESH_METHOD', default => 'javascript', description => 'What method windows should use to refresh themselves', help => q` Many windows in Javascript need to refresh themselves to keep their information current. This option determines what method they should use to do this. Choosing 'javascript' means that each window will have a short JavaScript statement in with a timer to prompt the refresh. This is the most compatible method. Choosing 'http' means the refresh instruction is put in the HTTP header. This is a cleaner method but refreshes are interrupted or cancelled when a link in the window is clicked meaning that the window will no longer refresh and this would have to be done manually. `, type => { db_type =>'string', hint =>'javascript|http', pattern =>qr|^([jh])|i, format =>q( $1 =~ /^j/ ? 'javascript' : 'http' ) }, category => 'hidden', }, { name => 'ZM_WEB_EVENT_SORT_FIELD', default => 'DateTime', description => 'Default field the event lists are sorted by', help => q` Events in lists can be initially ordered in any way you want. This option controls what field is used to sort them. You can modify this ordering from filters or by clicking on headings in the lists themselves. Bear in mind however that the 'Prev' and 'Next' links, when scrolling through events, relate to the ordering in the lists and so not always to time based ordering. `, type => { db_type =>'string', hint =>'Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', pattern =>qr|.|, format =>q( $1 ) }, category => 'web', }, { name => 'ZM_WEB_EVENT_SORT_ORDER', default => 'asc', description => 'Default order the event lists are sorted by', help => q` Events in lists can be initially ordered in any way you want. This option controls what order (ascending or descending) is used to sort them. You can modify this ordering from filters or by clicking on headings in the lists themselves. Bear in mind however that the 'Prev' and 'Next' links, when scrolling through events, relate to the ordering in the lists and so not always to time based ordering. `, type => { db_type =>'string', hint =>'asc|desc', pattern =>qr|^([ad])|i, format =>q( $1 =~ /^a/i ? 'asc' : 'desc' ) }, category => 'web', }, { name => 'ZM_WEB_EVENTS_PER_PAGE', default => '25', description => 'How many events to list per page in paged mode', help => q` In the event list view you can either list all events or just a page at a time. This option controls how many events are listed per page in paged mode and how often to repeat the column headers in non-paged mode. `, type => $types{integer}, category => 'web', }, { name => 'ZM_WEB_LIST_THUMBS', default => 'no', description => 'Display mini-thumbnails of event images in event lists', help => q` Ordinarily the event lists just display text details of the events to save space and time. By switching this option on you can also display small thumbnails to help you identify events of interest. The size of these thumbnails is controlled by the following two options. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_WEB_LIST_THUMB_WIDTH', default => '48', description => 'The width of the thumbnails that appear in the event lists', help => q` This options controls the width of the thumbnail images that appear in the event lists. It should be fairly small to fit in with the rest of the table. If you prefer you can specify a height instead in the next option but you should only use one of the width or height and the other option should be set to zero. If both width and height are specified then width will be used and height ignored. `, type => $types{integer}, requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], category => 'web', }, { name => 'ZM_WEB_LIST_THUMB_HEIGHT', default => '0', description => 'The height of the thumbnails that appear in the event lists', help => q` This options controls the height of the thumbnail images that appear in the event lists. It should be fairly small to fit in with the rest of the table. If you prefer you can specify a width instead in the previous option but you should only use one of the width or height and the other option should be set to zero. If both width and height are specified then width will be used and height ignored. `, type => $types{integer}, requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], category => 'web', }, { name => 'ZM_WEB_USE_OBJECT_TAGS', default => 'yes', description => 'Wrap embed in object tags for media content', help => q` There are two methods of including media content in web pages. The most common way is use the EMBED tag which is able to give some indication of the type of content. However this is not a standard part of HTML. The official method is to use OBJECT tags which are able to give more information allowing the correct media viewers etc to be loaded. However these are less widely supported and content may be specifically tailored to a particular platform or player. This option controls whether media content is enclosed in EMBED tags only or whether, where appropriate, it is additionally wrapped in OBJECT tags. Currently OBJECT tags are only used in a limited number of circumstances but they may become more widespread in the future. It is suggested that you leave this option on unless you encounter problems playing some content. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_WEB_XFRAME_WARN', default => 'yes', description => 'Warn when website X-Frame-Options is set to sameorigin', help => q` When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extenstion has ben installed, the end user may choose to turn this warning off. `, type => $types{boolean}, category => 'web', }, { name => 'ZM_WEB_FILTER_SOURCE', default => 'Hostname', description => 'How to filter information in the source column.', help => q` This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results. `, type => { db_type =>'string', hint =>'None|Hostname|NoCredentials', pattern =>qr|^([NH])|i, format =>q( ($1 =~ /^Non/) ? 'None' : ($1 =~ /^H/ ? 'Hostname' : 'NoCredentials' ) ) }, category => 'web', }, { name => 'ZM_WEB_H_REFRESH_MAIN', default => '60', introduction => q` There are now a number of options that are grouped into bandwidth categories, this allows you to configure the ZoneMinder client to work optimally over the various access methods you might to access the client.\n\nThe next few options control what happens when the client is running in 'high' bandwidth mode. You should set these options for when accessing the ZoneMinder client over a local network or high speed link. In most cases the default values will be suitable as a starting point. `, description => 'How often (in seconds) the main console window should refresh itself', help => q` The main console window lists a general status and the event totals for all monitors. This is not a trivial task and should not be repeated too frequently or it may affect the performance of the rest of the system. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_REFRESH_NAVBAR', default => '5', description => 'How often (in seconds) the navigation header should refresh itself', help => q` The navigation header contains the general status information about server load and storage space. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_REFRESH_CYCLE', default => '10', description => 'How often (in seconds) the cycle watch window swaps to the next monitor', help => q` The cycle watch window is a method of continuously cycling between images from all of your monitors. This option determines how often to refresh with a new image. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_REFRESH_IMAGE', default => '3', description => 'How often (in seconds) the watched image is refreshed (if not streaming)', help => q` The live images from a monitor can be viewed in either streamed or stills mode. This option determines how often a stills image is refreshed, it has no effect if streaming is selected. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_REFRESH_STATUS', default => '1', description => 'How often (in seconds) the status refreshes itself in the watch window', help => q` The monitor window is actually made from several frames. The one in the middle merely contains a monitor status which needs to refresh fairly frequently to give a true indication. This option determines that frequency. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_REFRESH_EVENTS', default => '5', description => 'How often (in seconds) the event listing is refreshed in the watch window', help => q` The monitor window is actually made from several frames. The lower framme contains a listing of the last few events for easy access. This option determines how often this is refreshed. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_CAN_STREAM', default => 'auto', description => 'Override the automatic detection of browser streaming capability', help => q` If you know that your browser can handle image streams of the type 'multipart/x-mixed-replace' but ZoneMinder does not detect this correctly you can set this option to ensure that the stream is delivered with or without the use of the Cambozola plugin. Selecting 'yes' will tell ZoneMinder that your browser can handle the streams natively, 'no' means that it can't and so the plugin will be used while 'auto' lets ZoneMinder decide. `, type => $types{tristate}, category => 'highband', }, { name => 'ZM_WEB_H_STREAM_METHOD', default => 'jpeg', description => 'Which method should be used to send video streams to your browser.', help => q` ZoneMinder can be configured to use either mpeg encoded video or a series or still jpeg images when sending video streams. This option defines which is used. If you choose mpeg you should ensure that you have the appropriate plugins available on your browser whereas choosing jpeg will work natively on Mozilla and related browsers and with a Java applet on Internet Explorer `, type => { db_type =>'string', hint =>'mpeg|jpeg', pattern =>qr|^([mj])|i, format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, category => 'highband', }, { name => 'ZM_WEB_H_DEFAULT_SCALE', default => '100', description => 'What the default scaling factor applied to \'live\' or \'event\' views is (%)', help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large dimensions or a slow link you may prefer to reduce this size, alternatively for small monitors you can enlarge it. This options lets you specify what the default scaling factor will be. It is expressed as a percentage so 100 is normal size, 200 is double size etc. `, type => { db_type =>'integer', hint =>'25|33|50|75|100|150|200|300|400', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, category => 'highband', }, { name => 'ZM_WEB_H_DEFAULT_RATE', default => '100', description => 'What the default replay rate factor applied to \'event\' views is (%)', help => q` Normally ZoneMinder will display 'event' streams at their native rate, i.e. as close to real-time as possible. However if you have long events it is often convenient to replay them at a faster rate for review. This option lets you specify what the default replay rate will be. It is expressed as a percentage so 100 is normal rate, 200 is double speed etc. `, type => { db_type =>'integer', hint =>'25|50|100|150|200|400|1000|2500|5000|10000', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, category => 'highband', }, { name => 'ZM_WEB_H_VIDEO_BITRATE', default => '150000', description => 'What the bitrate of the video encoded stream should be set to', help => q` When encoding real video via the ffmpeg library a bit rate can be specified which roughly corresponds to the available bandwidth used for the stream. This setting effectively corresponds to a 'quality' setting for the video. A low value will result in a blocky image whereas a high value will produce a clearer view. Note that this setting does not control the frame rate of the video however the quality of the video produced is affected both by this setting and the frame rate that the video is produced at. A higher frame rate at a particular bit rate result in individual frames being at a lower quality. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_VIDEO_MAXFPS', default => '30', description => 'What the maximum frame rate for streamed video should be', help => q` When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. This option allows you to limit the maximum frame rate to ensure that video quality is maintained. An additional advantage is that encoding video at high frame rates is a processor intensive task when for the most part a very high frame rate offers little perceptible improvement over one that has a more manageable resource requirement. Note, this option is implemented as a cap beyond which binary reduction takes place. So if you have a device capturing at 15fps and set this option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_H_SCALE_THUMBS', default => 'no', description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the browser to conserve bandwidth at the cost of cpu on the server. Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. `, type => $types{boolean}, category => 'highband', }, { name => 'ZM_WEB_H_EVENTS_VIEW', default => 'events', description => 'What the default view of multiple events should be.', help => q` Stored events can be viewed in either an events list format or in a timeline based one. This option sets the default view that will be used. Choosing one view here does not prevent the other view being used as it will always be selectable from whichever view is currently being used. `, type => { db_type =>'string', hint =>'events|timeline', pattern =>qr|^([lt])|i, format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, category => 'highband', }, { name => 'ZM_WEB_H_SHOW_PROGRESS', default => 'yes', description => 'Show the progress of replay in event view.', help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to specific points in the event, but can can also dynamically update to display the current progress of the event replay itself. This progress is calculated from the actual event duration and is not directly linked to the replay itself, so on limited bandwidth connections may be out of step with the replay. This option allows you to turn off the progress display, whilst still keeping the navigation aspect, where bandwidth prevents it functioning effectively. `, type => $types{boolean}, category => 'highband', }, { name => 'ZM_WEB_H_AJAX_TIMEOUT', default => '3000', description => 'How long to wait for Ajax request responses (ms)', help => q` The newer versions of the live feed and event views use Ajax to request information from the server and populate the views dynamically. This option allows you to specify a timeout if required after which requests are abandoned. A timeout may be necessary if requests would overwise hang such as on a slow connection. This would tend to consume a lot of browser memory and make the interface unresponsive. Ordinarily no requests should timeout so this setting should be set to a value greater than the slowest expected response. This value is in milliseconds but if set to zero then no timeout will be used. `, type => $types{integer}, category => 'highband', }, { name => 'ZM_WEB_M_REFRESH_MAIN', default => '300', description => 'How often (in seconds) the main console window should refresh itself', help => q` The main console window lists a general status and the event totals for all monitors. This is not a trivial task and should not be repeated too frequently or it may affect the performance of the rest of the system. `, type => $types{integer}, introduction => q` The next few options control what happens when the client is running in 'medium' bandwidth mode. You should set these options for when accessing the ZoneMinder client over a slower cable or DSL link. In most cases the default values will be suitable as a starting point. `, category => 'medband', }, { name => 'ZM_WEB_M_REFRESH_NAVBAR', default => '15', description => 'How often (in seconds) the navigation header should refresh itself', help => q` The navigation header contains the general status information about server load and storage space. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_M_REFRESH_CYCLE', default => '20', description => 'How often (in seconds) the cycle watch window swaps to the next monitor', help => q` The cycle watch window is a method of continuously cycling between images from all of your monitors. This option determines how often to refresh with a new image. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_M_REFRESH_IMAGE', default => '10', description => 'How often (in seconds) the watched image is refreshed (if not streaming)', help => q` The live images from a monitor can be viewed in either streamed or stills mode. This option determines how often a stills image is refreshed, it has no effect if streaming is selected. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_M_REFRESH_STATUS', default => '5', description => 'How often (in seconds) the status refreshes itself in the watch window', help => q` The monitor window is actually made from several frames. The one in the middle merely contains a monitor status which needs to refresh fairly frequently to give a true indication. This option determines that frequency. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_M_REFRESH_EVENTS', default => '60', description => 'How often (in seconds) the event listing is refreshed in the watch window', help => q` The monitor window is actually made from several frames. The lower framme contains a listing of the last few events for easy access. This option determines how often this is refreshed. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_M_CAN_STREAM', default => 'auto', description => 'Override the automatic detection of browser streaming capability', help => q` If you know that your browser can handle image streams of the type 'multipart/x-mixed-replace' but ZoneMinder does not detect this correctly you can set this option to ensure that the stream is delivered with or without the use of the Cambozola plugin. Selecting 'yes' will tell ZoneMinder that your browser can handle the streams natively, 'no' means that it can't and so the plugin will be used while 'auto' lets ZoneMinder decide. `, type => $types{tristate}, category => 'medband', }, { name => 'ZM_WEB_M_STREAM_METHOD', default => 'jpeg', description => 'Which method should be used to send video streams to your browser.', help => q` ZoneMinder can be configured to use either mpeg encoded video or a series or still jpeg images when sending video streams. This option defines which is used. If you choose mpeg you should ensure that you have the appropriate plugins available on your browser whereas choosing jpeg will work natively on Mozilla and related browsers and with a Java applet on Internet Explorer `, type => { db_type =>'string', hint =>'mpeg|jpeg', pattern =>qr|^([mj])|i, format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, category => 'medband', }, { name => 'ZM_WEB_M_DEFAULT_SCALE', default => '100', description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large dimensions or a slow link you may prefer to reduce this size, alternatively for small monitors you can enlarge it. This options lets you specify what the default scaling factor will be. It is expressed as a percentage so 100 is normal size, 200 is double size etc. `, type => { db_type =>'integer', hint =>'25|33|50|75|100|150|200|300|400', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, category => 'medband', }, { name => 'ZM_WEB_M_DEFAULT_RATE', default => '100', description => q`What the default replay rate factor applied to 'event' views is (%)`, help => q` Normally ZoneMinder will display 'event' streams at their native rate, i.e. as close to real-time as possible. However if you have long events it is often convenient to replay them at a faster rate for review. This option lets you specify what the default replay rate will be. It is expressed as a percentage so 100 is normal rate, 200 is double speed etc. `, type => { db_type =>'integer', hint =>'25|50|100|150|200|400|1000|2500|5000|10000', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, category => 'medband', }, { name => 'ZM_WEB_M_VIDEO_BITRATE', default => '75000', description => 'What the bitrate of the video encoded stream should be set to', help => q` When encoding real video via the ffmpeg library a bit rate can be specified which roughly corresponds to the available bandwidth used for the stream. This setting effectively corresponds to a 'quality' setting for the video. A low value will result in a blocky image whereas a high value will produce a clearer view. Note that this setting does not control the frame rate of the video however the quality of the video produced is affected both by this setting and the frame rate that the video is produced at. A higher frame rate at a particular bit rate result in individual frames being at a lower quality. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_M_VIDEO_MAXFPS', default => '10', description => 'What the maximum frame rate for streamed video should be', help => q` When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. This option allows you to limit the maximum frame rate to ensure that video quality is maintained. An additional advantage is that encoding video at high frame rates is a processor intensive task when for the most part a very high frame rate offers little perceptible improvement over one that has a more manageable resource requirement. Note, this option is implemented as a cap beyond which binary reduction takes place. So if you have a device capturing at 15fps and set this option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_M_SCALE_THUMBS', default => 'yes', description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the browser to conserve bandwidth at the cost of cpu on the server. Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. `, type => $types{boolean}, category => 'medband', }, { name => 'ZM_WEB_M_EVENTS_VIEW', default => 'events', description => 'What the default view of multiple events should be.', help => q` Stored events can be viewed in either an events list format or in a timeline based one. This option sets the default view that will be used. Choosing one view here does not prevent the other view being used as it will always be selectable from whichever view is currently being used. `, type => { db_type =>'string', hint =>'events|timeline', pattern =>qr|^([lt])|i, format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, category => 'medband', }, { name => 'ZM_WEB_M_SHOW_PROGRESS', default => 'yes', description => 'Show the progress of replay in event view.', help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to specific points in the event, but can can also dynamically update to display the current progress of the event replay itself. This progress is calculated from the actual event duration and is not directly linked to the replay itself, so on limited bandwidth connections may be out of step with the replay. This option allows you to turn off the progress display, whilst still keeping the navigation aspect, where bandwidth prevents it functioning effectively. `, type => $types{boolean}, category => 'medband', }, { name => 'ZM_WEB_M_AJAX_TIMEOUT', default => '5000', description => 'How long to wait for Ajax request responses (ms)', help => q` The newer versions of the live feed and event views use Ajax to request information from the server and populate the views dynamically. This option allows you to specify a timeout if required after which requests are abandoned. A timeout may be necessary if requests would overwise hang such as on a slow connection. This would tend to consume a lot of browser memory and make the interface unresponsive. Ordinarily no requests should timeout so this setting should be set to a value greater than the slowest expected response. This value is in milliseconds but if set to zero then no timeout will be used. `, type => $types{integer}, category => 'medband', }, { name => 'ZM_WEB_L_REFRESH_MAIN', default => '300', description => 'How often (in seconds) the main console window should refresh itself', introduction => q` The next few options control what happens when the client is running in 'low' bandwidth mode. You should set these options for when accessing the ZoneMinder client over a modem or slow link. In most cases the default values will be suitable as a starting point. `, help => q` The main console window lists a general status and the event totals for all monitors. This is not a trivial task and should not be repeated too frequently or it may affect the performance of the rest of the system. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_REFRESH_NAVBAR', default => '35', description => 'How often (in seconds) the navigation header should refresh itself', help => q` The navigation header contains the general status information about server load and storage space. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_REFRESH_CYCLE', default => '30', description => 'How often (in seconds) the cycle watch window swaps to the next monitor', help => q` The cycle watch window is a method of continuously cycling between images from all of your monitors. This option determines how often to refresh with a new image. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_REFRESH_IMAGE', default => '15', description => 'How often (in seconds) the watched image is refreshed (if not streaming)', help => q` The live images from a monitor can be viewed in either streamed or stills mode. This option determines how often a stills image is refreshed, it has no effect if streaming is selected. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_REFRESH_STATUS', default => '10', description => 'How often (in seconds) the status refreshes itself in the watch window', help => q` The monitor window is actually made from several frames. The one in the middle merely contains a monitor status which needs to refresh fairly frequently to give a true indication. This option determines that frequency. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_REFRESH_EVENTS', default => '180', description => 'How often (in seconds) the event listing is refreshed in the watch window', help => q` The monitor window is actually made from several frames. The lower framme contains a listing of the last few events for easy access. This option determines how often this is refreshed. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_CAN_STREAM', default => 'auto', description => 'Override the automatic detection of browser streaming capability', help => q` If you know that your browser can handle image streams of the type 'multipart/x-mixed-replace' but ZoneMinder does not detect this correctly you can set this option to ensure that the stream is delivered with or without the use of the Cambozola plugin. Selecting 'yes' will tell ZoneMinder that your browser can handle the streams natively, 'no' means that it can't and so the plugin will be used while 'auto' lets ZoneMinder decide. `, type => $types{tristate}, category => 'lowband', }, { name => 'ZM_WEB_L_STREAM_METHOD', default => 'jpeg', description => 'Which method should be used to send video streams to your browser.', help => q` ZoneMinder can be configured to use either mpeg encoded video or a series or still jpeg images when sending video streams. This option defines which is used. If you choose mpeg you should ensure that you have the appropriate plugins available on your browser whereas choosing jpeg will work natively on Mozilla and related browsers and with a Java applet on Internet Explorer `, type => { db_type =>'string', hint =>'mpeg|jpeg', pattern =>qr|^([mj])|i, format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, category => 'lowband', }, { name => 'ZM_WEB_L_DEFAULT_SCALE', default => '100', description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large dimensions or a slow link you may prefer to reduce this size, alternatively for small monitors you can enlarge it. This options lets you specify what the default scaling factor will be. It is expressed as a percentage so 100 is normal size, 200 is double size etc. `, type => { db_type =>'integer', hint =>'25|33|50|75|100|150|200|300|400', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, category => 'lowband', }, { name => 'ZM_WEB_L_DEFAULT_RATE', default => '100', description => q`What the default replay rate factor applied to 'event' views is (%)`, help => q` Normally ZoneMinder will display 'event' streams at their native rate, i.e. as close to real-time as possible. However if you have long events it is often convenient to replay them at a faster rate for review. This option lets you specify what the default replay rate will be. It is expressed as a percentage so 100 is normal rate, 200 is double speed etc. `, type => { db_type =>'integer', hint =>'25|50|100|150|200|400|1000|2500|5000|10000', pattern =>qr|^(\d+)$|, format=>q( $1 ) }, category => 'lowband', }, { name => 'ZM_WEB_L_VIDEO_BITRATE', default => '25000', description => 'What the bitrate of the video encoded stream should be set to', help => q` When encoding real video via the ffmpeg library a bit rate can be specified which roughly corresponds to the available bandwidth used for the stream. This setting effectively corresponds to a 'quality' setting for the video. A low value will result in a blocky image whereas a high value will produce a clearer view. Note that this setting does not control the frame rate of the video however the quality of the video produced is affected both by this setting and the frame rate that the video is produced at. A higher frame rate at a particular bit rate result in individual frames being at a lower quality. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_VIDEO_MAXFPS', default => '5', description => 'What the maximum frame rate for streamed video should be', help => q` When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. This option allows you to limit the maximum frame rate to ensure that video quality is maintained. An additional advantage is that encoding video at high frame rates is a processor intensive task when for the most part a very high frame rate offers little perceptible improvement over one that has a more manageable resource requirement. Note, this option is implemented as a cap beyond which binary reduction takes place. So if you have a device capturing at 15fps and set this option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_WEB_L_SCALE_THUMBS', default => 'yes', description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the browser to conserve bandwidth at the cost of cpu on the server. Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. `, type => $types{boolean}, category => 'lowband', }, { name => 'ZM_WEB_L_EVENTS_VIEW', default => 'events', description => 'What the default view of multiple events should be.', help => q` Stored events can be viewed in either an events list format or in a timeline based one. This option sets the default view that will be used. Choosing one view here does not prevent the other view being used as it will always be selectable from whichever view is currently being used. `, type => { db_type =>'string', hint =>'events|timeline', pattern =>qr|^([lt])|i, format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, category => 'lowband', }, { name => 'ZM_WEB_L_SHOW_PROGRESS', default => 'no', description => 'Show the progress of replay in event view.', help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to specific points in the event, but can can also dynamically update to display the current progress of the event replay itself. This progress is calculated from the actual event duration and is not directly linked to the replay itself, so on limited bandwidth connections may be out of step with the replay. This option allows you to turn off the progress display, whilst still keeping the navigation aspect, where bandwidth prevents it functioning effectively. `, type => $types{boolean}, category => 'lowband', }, { name => 'ZM_WEB_L_AJAX_TIMEOUT', default => '10000', description => 'How long to wait for Ajax request responses (ms)', help => q` The newer versions of the live feed and event views use Ajax to request information from the server and populate the views dynamically. This option allows you to specify a timeout if required after which requests are abandoned. A timeout may be necessary if requests would overwise hang such as on a slow connection. This would tend to consume a lot of browser memory and make the interface unresponsive. Ordinarily no requests should timeout so this setting should be set to a value greater than the slowest expected response. This value is in milliseconds but if set to zero then no timeout will be used. `, type => $types{integer}, category => 'lowband', }, { name => 'ZM_DYN_LAST_VERSION', default => '', description => 'What the last version of ZoneMinder recorded from zoneminder.com is', help => '', type => $types{string}, readonly => 1, category => 'dynamic', }, { name => 'ZM_DYN_CURR_VERSION', default => '@VERSION@', description => q` What the effective current version of ZoneMinder is, might be different from actual if versions ignored `, help => '', type => $types{string}, readonly => 1, category => 'dynamic', }, { name => 'ZM_DYN_DB_VERSION', default => '@VERSION@', description => 'What the version of the database is, from zmupdate', help => '', type => $types{string}, readonly => 1, category => 'dynamic', }, { name => 'ZM_DYN_LAST_CHECK', default => '', description => 'When the last check for version from zoneminder.com was', help => '', type => $types{integer}, readonly => 1, category => 'dynamic', }, { name => 'ZM_DYN_NEXT_REMINDER', default => '', description => 'When the earliest time to remind about versions will be', help => '', type => $types{string}, readonly => 1, category => 'dynamic', }, { name => 'ZM_DYN_DONATE_REMINDER_TIME', default => 0, description => 'When the earliest time to remind about donations will be', help => '', type => $types{integer}, readonly => 1, category => 'dynamic', }, { name => 'ZM_DYN_SHOW_DONATE_REMINDER', default => 'yes', description => 'Remind about donations or not', help => '', type => $types{boolean}, readonly => 1, category => 'dynamic', }, { name => 'ZM_SHOW_PRIVACY', default => 'yes', description => 'Present the privacy statment', help => '', type => $types{boolean}, readonly => 1, category => 'dynamic', }, { name => 'ZM_SSMTP_MAIL', default => 'no', description => q` Use a SSMTP mail server if available. NEW_MAIL_MODULES must be enabled `, requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' }, { name => 'ZM_OPT_MESSAGE', value => 'yes' }, { name => 'ZM_NEW_MAIL_MODULES', value => 'yes' } ], help => q` SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder) for setup and configuration help. `, type => $types{boolean}, category => 'mail', }, { name => 'ZM_SSMTP_PATH', default => '', description => 'SSMTP executable path', requires => [{ name => 'ZM_SSMTP_MAIL', value => 'yes' }], help => q` Recommend setting the path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. `, type => $types{string}, category => 'mail', }, ); our %options_hash = map { ( $_->{name}, $_ ) } @options; # This function should never need to be called explicitly, except if # this module is 'require'd rather than 'use'd. See zmconfgen.pl. sub initialiseConfig { return if ( $configInitialised ); # Do some initial data munging to finish the data structures # Create option ids my $option_id = 0; foreach my $option ( @options ) { if ( defined($option->{default}) ) { $option->{value} = $option->{default} } else { $option->{value} = ''; } #next if ( $option->{category} eq 'hidden' ); $option->{id} = $option_id++; } $configInitialised = 1; } 1; __END__ =head1 NAME ZoneMinder::ConfigData - ZoneMinder Configuration Data module =head1 SYNOPSIS use ZoneMinder::ConfigData; use ZoneMinder::ConfigData qw(:all); loadConfigFromDB(); saveConfigToDB(); =head1 DESCRIPTION The ZoneMinder:ConfigData module contains the master definition of the ZoneMinder configuration options as well as helper methods. This module is intended for specialist confguration management and would not normally be used by end users. The configuration held in this module, which was previously in zmconfig.pl, includes the name, default value, description, help text, type and category for each option, as well as a number of additional fields in a small number of cases. =head1 METHODS =over 4 =item loadConfigFromDB (); Loads existing configuration from the database (if any) and merges it with the definitions held in this module. This results in the merging of any new configuration and the removal of any deprecated configuration while preserving the existing values of every else. =item saveConfigToDB (); Saves configuration held in memory to the database. The act of loading and saving configuration is a convenient way to ensure that the configuration held in the database corresponds with the most recent definitions and that all components are using the same set of configuration. =back =head2 EXPORT None by default. The :data tag will export the various configuration data structures The :functions tag will export the helper functions. The :all tag will export all above symbols. =head1 SEE ALSO http://www.zoneminder.com =head1 AUTHOR Philip Coombes, Ephilip.coombes@zoneminder.comE =head1 COPYRIGHT AND LICENSE Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut ZoneMinder-1.32.2/scripts/ZoneMinder/Makefile.PL0000644000000000000000000000117413365153155020074 0ustar rootrootuse 5.006; use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( NAME => 'ZoneMinder', VERSION_FROM => 'lib/ZoneMinder/Base.pm', # finds $VERSION PREREQ_PM => {}, # e.g., Module::Name => 1.1 # No need to specify perl modules. MakeMaker will find them automatically ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'lib/ZoneMinder.pm', # retrieve abstract from module AUTHOR => 'Philip Coombes ') : ()), ); ZoneMinder-1.32.2/scripts/ZoneMinder/INSTALL.SKIP0000644000000000000000000000001213365153155017706 0ustar rootroot\.pm\.in$ ZoneMinder-1.32.2/scripts/ZoneMinder/MANIFEST0000644000000000000000000000022413365153155017246 0ustar rootrootChanges Makefile.PL MANIFEST README t/ZoneMinder.t lib/ZoneMinder.pm META.yml Module meta-data (added by MakeMaker) ZoneMinder-1.32.2/scripts/ZoneMinder/MANIFEST.SKIP0000644000000000000000000000001213365153155020006 0ustar rootroot\.pm\.in$ ZoneMinder-1.32.2/scripts/ZoneMinder/t/0000755000000000000000000000000013365153155016362 5ustar rootrootZoneMinder-1.32.2/scripts/ZoneMinder/t/ZoneMinder.t0000644000000000000000000000077313365153155020630 0ustar rootroot# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl ZoneMinder.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test; BEGIN { plan tests => 1 }; use ZoneMinder; ok(1); # If we made it this far, we're ok. ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. ZoneMinder-1.32.2/scripts/ZoneMinder/CMakeLists.txt0000644000000000000000000000425213365153155020662 0ustar rootroot# CMakeLists.txt for the ZoneMinder perl module. # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Changes" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/META.yml" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/README" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/t" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) # Create files from the .in files configure_file(lib/ZoneMinder/Base.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/Base.pm" @ONLY) configure_file(lib/ZoneMinder/Config.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/Config.pm" @ONLY) configure_file(lib/ZoneMinder/Memory.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/Memory.pm" @ONLY) configure_file(lib/ZoneMinder/ConfigData.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/ConfigData.pm" @ONLY) configure_file(lib/ZoneMinder/ONVIF.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/ONVIF.pm" @ONLY) if(CMAKE_VERBOSE_MAKEFILE) set(MAKEMAKER_NOECHO_COMMAND "") else(CMAKE_VERBOSE_MAKEFILE) set(MAKEMAKER_NOECHO_COMMAND "NOECHO=\"1>/dev/null\"") endif(CMAKE_VERBOSE_MAKEFILE) # Add build target for the perl modules add_custom_target(zmperlmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl modules") # Add install target for the perl modules install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/" DESTINATION "/") # Add additional files and directories to make clean set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "output;blib;pm_to_blib;MakefilePerl") ZoneMinder-1.32.2/scripts/ZoneMinder/META.yml0000644000000000000000000000046713365153155017377 0ustar rootroot# http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: ZoneMinder version: 1.23.2 version_from: lib/ZoneMinder/Base.pm installdirs: site requires: distribution_type: module generated_by: ExtUtils::MakeMaker version 6.17 ZoneMinder-1.32.2/scripts/CMakeLists.txt0000644000000000000000000000621413365153155016610 0ustar rootroot# CMakeLists.txt for the ZoneMinder perl scripts. # Process the perl modules subdirectory add_subdirectory(ZoneMinder) # Create files from the .in files configure_file(zmaudit.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" @ONLY) configure_file(zmcontrol.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" @ONLY) configure_file(zmdc.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" @ONLY) configure_file(zmfilter.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" @ONLY) configure_file(zmonvif-probe.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" @ONLY) configure_file(zmpkg.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" @ONLY) configure_file(zmtrack.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" @ONLY) configure_file(zmtrigger.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" @ONLY) configure_file(zmupdate.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" @ONLY) configure_file(zmvideo.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" @ONLY) configure_file(zmwatch.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" @ONLY) configure_file(zmstats.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" @ONLY) configure_file(zmcamtool.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" @ONLY) configure_file(zmsystemctl.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" @ONLY) configure_file(zmtelemetry.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" @ONLY) if(NOT ZM_NO_X10) configure_file(zmx10.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" @ONLY) endif(NOT ZM_NO_X10) #configure_file(zmdbbackup.in zmdbbackup @ONLY) #configure_file(zmdbrestore.in zmdbrestore @ONLY) configure_file(zm.in "${CMAKE_CURRENT_BINARY_DIR}/zm" @ONLY) #configure_file(zmeventdump.in zmeventdump @ONLY) # Generate man files for the perl scripts destined for the bin folder file(GLOB perlscripts "*.pl") FOREACH(PERLSCRIPT ${perlscripts}) get_filename_component(PERLSCRIPTNAME ${PERLSCRIPT} NAME) POD2MAN(${PERLSCRIPT} zoneminder-${PERLSCRIPTNAME} 8) ENDFOREACH(PERLSCRIPT ${perlscripts}) # Install the perl scripts install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) if(NOT ZM_NO_X10) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endif(NOT ZM_NO_X10) if(WITH_SYSTEMD) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endif(WITH_SYSTEMD) ZoneMinder-1.32.2/scripts/zmwatch.pl.in0000644000000000000000000001552113365153155016470 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder WatchDog Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmwatch.pl - ZoneMinder WatchDog Script =head1 SYNOPSIS zmwatch.pl =head1 DESCRIPTION This does some basic setup for ZoneMinder to run and then periodically checks the fps output of the active daemons to check they haven't locked up. If they have then they are killed and restarted =cut use strict; use bytes; # ========================================================================== # # These are the elements you can edit to suit your installation # # ========================================================================== use constant START_DELAY => 30; # To give everything else time to start # ========================================================================== # # Don't change anything below here # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use ZoneMinder::Storage; use POSIX; use DBI; use autouse 'Data::Dumper'=>qw(Dumper); $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); Info('Watchdog starting, pausing for '.START_DELAY.' seconds'); sleep(START_DELAY); my $dbh = zmDbConnect(); my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'SELECT * FROM Monitors'; my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); while( 1 ) { while ( ! ( $dbh and $dbh->ping() ) ) { if ( ! ( $dbh = zmDbConnect() ) ) { sleep($Config{ZM_WATCH_CHECK_INTERVAL}); } } my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) or Fatal('Can\'t execute: '.$sth->errstr()); while( my $monitor = $sth->fetchrow_hashref() ) { next if $monitor->{Function} eq 'None'; next if $monitor->{Type} eq 'WebSite'; my $now = time(); my $restart = 0; if ( zmMemVerify($monitor) ) { # Check we have got an image recently my $capture_time = zmGetLastWriteTime($monitor); if ( !defined($capture_time) ) { # Can't read from shared data Debug('LastWriteTime is not defined.'); zmMemInvalidate($monitor); next; } Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time."); if ( !$capture_time ) { my $startup_time = zmGetStartupTime($monitor); if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) { Info( "Restarting capture daemon for $$monitor{Name}, no image since startup. ". "Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}" ); $restart = 1; } else { # We can't get the last capture time so can't be sure it's died, it might just be starting up. zmMemInvalidate($monitor); next; } } if ( ! $restart ) { my $max_image_delay = ( $monitor->{MaxFPS} &&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}<1) ) ? (3/$monitor->{MaxFPS}) : $Config{ZM_WATCH_MAX_DELAY} ; my $image_delay = $now-$capture_time; Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay"); if ( $image_delay > $max_image_delay ) { Info("Restarting capture daemon for " .$monitor->{Name}.", time since last capture $image_delay seconds ($now-$capture_time)" ); $restart = 1; } } # end if ! restart } else { Info("Restarting capture daemon for $monitor->{Name}, shared data not valid"); $restart = 1; } if ( $restart ) { # Because zma depends on zmc, and zma can hold the shm in place, preventing zmc from using the space in /dev/shm, # we need to stop zma before restarting zmc. runCommand("zmdc.pl stop zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; my $command; if ( $monitor->{Type} eq 'Local' ) { $command = "zmdc.pl restart zmc -d $monitor->{Device}"; } else { $command = "zmdc.pl restart zmc -m $monitor->{Id}"; } runCommand($command); runCommand("zmdc.pl start zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; } elsif ( $monitor->{Function} ne 'Monitor' ) { # Now check analysis daemon $restart = 0; # Check we have got an image recently my $image_time = zmGetLastReadTime($monitor); if ( !defined($image_time) ) { # Can't read from shared data $restart = 1; Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); } elsif ( !$image_time ) { # We can't get the last capture time so can't be sure it's died. $restart = 1; Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); } else { my $max_image_delay = ( $monitor->{MaxFPS} &&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}<1) ) ? (3/$monitor->{MaxFPS}) : $Config{ZM_WATCH_MAX_DELAY} ; my $image_delay = $now-$image_time; Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay"); if ( $image_delay > $max_image_delay ) { Info("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting," ." time since last analysis $image_delay seconds ($now-$image_time)" ); $restart = 1; } } if ( $restart ) { Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}"); my $command = 'zmdc.pl restart zma -m '.$monitor->{Id}; runCommand($command); } # end if restart } # end if check analysis daemon # Prevent open handles building up if we have connect to shared memory zmMemInvalidate($monitor); # Close our file handle to the zmc process we are about to end } # end foreach monitor sleep($Config{ZM_WATCH_CHECK_INTERVAL}); } # end while (1) Info("Watchdog exiting"); exit(); 1; __END__ ZoneMinder-1.32.2/scripts/zmtelemetry.pl.in0000644000000000000000000003041113365153155017367 0ustar rootroot#!/usr/bin/perl -w # # ========================================================================== # # ZoneMinder Telemetry Upload Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== use strict; use bytes; @EXTRA_PERL_LIB@ use ZoneMinder; use DBI; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use LWP::UserAgent; use Sys::MemInfo qw(totalmem); use Sys::CPU qw(cpu_count); use POSIX qw(strftime uname); use JSON::MaybeXS; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Setting these as contants for now. my $help = 0; my $force = 0; # Interval between version checks my $interval; my $version; GetOptions( force => \$force, help => \$help, interval => \$interval, version => \$version ); if ( $version ) { print( ZoneMinder::Base::ZM_VERSION . "\n"); exit(0); } if ( $help ) { pod2usage(-exitstatus => -1); } if ( ! defined $interval ) { $interval = eval($Config{ZM_TELEMETRY_INTERVAL}); } if ( !($Config{ZM_TELEMETRY_DATA} or $force) ) { print "ZoneMinder Telemetry Agent not enabled. Exiting.\n"; exit(0); } print 'ZoneMinder Telemetry Agent starting at '.strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n"; my $lastCheck = $Config{ZM_TELEMETRY_LAST_UPLOAD}; while( 1 ) { my $now = time(); my $since_last_check = $now-$lastCheck; Debug(" Last Check time (now($now) - lastCheck($lastCheck)) = $since_last_check > interval($interval) or force($force)"); if ( $since_last_check < 0 ) { Warning( 'Seconds since last check is negative! Which means that lastCheck is in the future!' ); next; } if ( ( ($since_last_check) > $interval ) or $force ) { print "Collecting data to send to ZoneMinder Telemetry server.\n"; my $dbh = zmDbConnect(); # Build the telemetry hash # We should keep *BSD systems in mind when calling system commands my %telemetry; $telemetry{uuid} = getUUID($dbh); ($telemetry{city}, $telemetry{region}, $telemetry{country}, $telemetry{latitude}, $telemetry{longitude}) = getGeo(); $telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() ); $telemetry{monitor_count} = countQuery($dbh,'Monitors'); $telemetry{event_count} = countQuery($dbh,'Events'); $telemetry{architecture} = runSysCmd('uname -p'); ($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro(); $telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION; $telemetry{system_memory} = totalmem(); $telemetry{processor_count} = cpu_count(); $telemetry{monitors} = getMonitorRef($dbh); Info('Sending data to ZoneMinder Telemetry server.'); my $result = jsonEncode(\%telemetry); if ( sendData($result) ) { my $sql = q`UPDATE Config SET Value = ? WHERE Name = 'ZM_TELEMETRY_LAST_UPLOAD'`; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute($now) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); $Config{ZM_TELEMETRY_LAST_UPLOAD} = $now; } zmDbDisconnect(); } elsif ( -t STDIN ) { print "ZoneMinder Telemetry Agent sleeping for $interval seconds because ($now-$lastCheck=$since_last_check > $interval\n"; } $lastCheck = $now; sleep($interval); } # end while print 'ZoneMinder Telemetry Agent exiting at '.strftime('%y/%m/%d %H:%M:%S', localtime())."\n"; ############### # SUBROUTINES # ############### # Find, verify, then run the supplied system command sub runSysCmd { my $msg = shift; my @arguments = split(/ /,$msg); chomp($arguments[0]); my $path = qx( which $arguments[0] ); my $status = $? >> 8; my $result = ''; if ( !$path || $status ) { Warning( "Cannot find the $arguments[0] executable." ); } else { chomp($path); $arguments[0] = $path; my $cmd = join(' ',@arguments); $result = qx( $cmd ); chomp($result); } return $result; } # Upload message data to ZoneMinder telemetry server sub sendData { my $msg = shift; my $ua = LWP::UserAgent->new; my $server_endpoint = $Config{ZM_TELEMETRY_SERVER_ENDPOINT}; if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { $ua->proxy( 'https', $Config{ZM_UPDATE_CHECK_PROXY} ); } Debug("Posting telemetry data to: $server_endpoint"); # set custom HTTP request header fields my $req = HTTP::Request->new(POST => $server_endpoint); $req->header('content-type' => 'application/x-www-form-urlencoded'); $req->header('content-length' => length($msg)); $req->header('connection' => 'Close'); $req->content($msg); my $resp = $ua->request($req); my $resp_msg = $resp->decoded_content; my $resp_code = $resp->code; if ($resp->is_success) { Info('Telemetry data uploaded successfully.'); Debug("Telemetry server upload success response message: $resp_msg"); } else { Warning("Telemetry server returned HTTP POST error code: $resp_code"); Debug("Telemetry server upload failure response message: $resp_msg"); } return $resp->is_success; } # Retrieves the UUID from the database. Creates a new UUID if one does not exist. sub getUUID { my $dbh = shift; my $uuid= ''; # Verify the current UUID is valid and not nil if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne '00000000-0000-0000-0000-000000000000' )) { $uuid = $Config{ZM_TELEMETRY_UUID}; } else { my $sql = 'SELECT uuid()'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); $uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array(); $sth->finish(); $sql = q`UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); } Debug("Using UUID of: $uuid"); return $uuid; } # Retrieve this server's general location information from a GeoIP database sub getGeo { my $unknown = 'Unknown'; my $endpoint = 'https://ipinfo.io/geo'; my $ua = LWP::UserAgent->new; my $req = HTTP::Request->new(GET => $endpoint); my $resp = $ua->request($req); my $resp_msg = $resp->decoded_content; my $resp_code = $resp->code; if ($resp->is_success) { my $content = decode_json( $resp_msg ); (my $latitude, my $longitude) = split /,/, $content->{loc}; return ($content->{city}, $content->{region}, $content->{country}, $latitude, $longitude); } else { Warning("Geoip data retrieval returned HTTP POST error code: $resp_code"); Debug("Geoip data retrieval failure response message: $resp_msg"); return ($unknown, $unknown, $unknown, $unknown); } } # As the name implies, just your average mysql count query sub countQuery { my $dbh = shift; my $table = shift; my $sql = "SELECT count(*) FROM $table"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my $count = $sth->fetchrow_array(); $sth->finish(); return $count } # Returns a reference to an array of hashes containing data from all monitors sub getMonitorRef { my $dbh = shift; my $sql = 'SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my $arrayref = $sth->fetchall_arrayref({}); return $arrayref; } sub getDistro { my $kernel = ''; my $distro = ''; my $version = ''; my @uname = uname(); if ( $uname[0] =~ /Linux/ ) { Debug('Linux distro detected.'); ($kernel, $distro, $version) = linuxDistro(); } elsif ( $uname[0] =~ /.*BSD/ ) { Debug('BSD distro detected.'); $kernel = $uname[3]; $distro = $uname[0]; $version = $uname[2]; } elsif ( $uname[0] =~ /Darwin/ ) { Debug('Mac OS distro detected.'); $kernel = $uname[3]; $distro = runSysCmd('sw_vers -productName'); $version = runSysCmd('sw_vers -productVersion'); } elsif ( $uname[0] =~ /SunOS|Solaris/ ) { Debug('Sun Solaris detected.'); $kernel = $uname[3]; $distro = $uname[1]; $version = $uname[2]; } else { Warning('ZoneMinder was unable to determine the host system. Please report.'); $kernel = 'Unknown'; $distro = 'Unknown'; $version = 'Unknown'; } return ($kernel, $distro, $version); } sub linuxDistro { my @uname = uname(); my $kernel = $uname[2]; my $distro = 'Unknown Linux Distro'; my $version = 'Unknown Linux Version'; my $found = 0; # os-release is the standard for many new distros based on systemd if ( -f '/etc/os-release' ) { open(my $RELFILE,'<','/etc/os-release') or die( "Can't Open file: $!\n" ); while (<$RELFILE>) { if ( /^NAME=(")?(.*)(?(1)\1|).*$/ ) { $distro = $2; $found = 1; } if ( /^VERSION_ID=(")?(.*)(?(1)\1|).*$/ ) { $version = $2; $found = 1; } } close $RELFILE; # exists on many distros but does not always contain useful information, such as redhat } elsif ( -f '/etc/lsb-release' ) { open(my $RELFILE,'<','/etc/lsb-release') or die( "Can't Open file: $!\n" ); while (<$RELFILE>) { if ( /^DISTRIB_DESCRIPTION=(")?(.*)(?(1)\1|).*$/ ) { $distro = $2; $found = 1; } if ( /^DISTRIB_RELEASE=(")?(.*)(?(1)\1|).*$/ ) { $version = $2; $found = 1; } } close $RELFILE; } # If all else fails, search through a list of known release files until we find one if ( !$found ) { my @releasefile = ('/etc/SuSE-release', '/etc/redhat-release', '/etc/redhat_version', '/etc/fedora-release', '/etc/slackware-release', '/etc/slackware-version', '/etc/debian_release', '/etc/debian_version', '/etc/mandrake-release', '/etc/yellowdog-release', '/etc/gentoo-release'); foreach (@releasefile) { if ( -f $_ ) { open(my $RELFILE,'<',$_) or die( "Can't Open file: $!\n" ); while (<$RELFILE>) { if ( /(.*).* (\d+\.?\d*) .*/ ) { $distro = $1; $version = $2; $found = 1; } } close $RELFILE; last; } } } if ( !$found ) { Warning('ZoneMinder was unable to determine Linux distro. Please report.'); } return ($kernel, $distro, $version); } 1; __END__ =head1 NAME zmtelemetry.pl - Send usage information to the ZoneMinder development team =head1 SYNOPSIS zmtelemetry.pl [--force] [--help] [--interval=seconds] [--version] =head1 DESCRIPTION This script collects usage information of the local system and sends it to the ZoneMinder development team. This data will be used to determine things like who and where our customers are, how big their systems are, the underlying hardware and operating system, etc. This is being done for the sole purpoase of creating a better product for our target audience. This script is intended to be completely transparent to the end user, and can be disabled from the web console under Options. =head1 OPTIONS --force Force the script to upload it's data instead of waiting for the defined interval since last upload. --help Display usage information --interval Override the default configured interval since last upload. The value should be given in seconds, but can be an expression such as 24*60*60. --version Output the version of ZoneMinder. =cut ZoneMinder-1.32.2/scripts/zmupdate.pl.in0000644000000000000000000012475713365153155016660 0ustar rootroot#!/usr/bin/perl -w # # ========================================================================== # # ZoneMinder Update Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmupdate.pl - check and upgrade ZoneMinder database =head1 SYNOPSIS zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p] =head1 DESCRIPTION This script just checks what the most recent release of ZoneMinder is at the the moment. It will eventually be responsible for applying and configuring upgrades etc, including on the fly upgrades. =head1 OPTIONS -c, --check - Check for updated versions of ZoneMinder -f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi --migrate-events - Update database structures as per USE_DEEP_STORAGE setting. -v, --version= - Force upgrade to the current version from -u, --user= - Alternate DB user with privileges to alter DB -p, --pass= - Password of alternate DB user with privileges to alter DB -d,--dir= - Directory containing update files if not in default build location -interactive - interact with the user -nointeractive - do not interact with the user =cut use strict; use bytes; use version; # ========================================================================== # # These are the elements you can edit to suit your installation # # ========================================================================== use constant CHECK_INTERVAL => (1*24*60*60); # Interval between version checks # ========================================================================== # # Don't change anything below here # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder::Base qw(:all); use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::General qw(:all); use ZoneMinder::Database qw(:all); use POSIX; use DBI; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)?$Config{ZM_DIR_EVENTS}:($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}); $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $web_uid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; my $use_log = (($> == 0) || ($> == $web_uid)); logInit( toFile=>$use_log?DEBUG:NOLOG ); logSetSignal(); my $interactive = 1; my $check = 0; my $freshen = 0; my $rename = 0; my $zoneFix = 0; my $migrateEvents = 0; my $version = ''; my $dbUser = $Config{ZM_DB_USER}; my $dbPass = $Config{ZM_DB_PASS}; my $updateDir = ''; GetOptions( 'check' =>\$check, 'freshen' =>\$freshen, 'rename' =>\$rename, 'zone-fix' =>\$zoneFix, 'migrate-events' =>\$migrateEvents, 'version=s' =>\$version, 'interactive!' =>\$interactive, 'user:s' =>\$dbUser, 'pass:s' =>\$dbPass, 'dir:s' =>\$updateDir ) or pod2usage(-exitstatus => -1); my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } ); $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; if ( ! ($check || $freshen || $rename || $zoneFix || $migrateEvents || $version) ) { if ( $Config{ZM_DYN_DB_VERSION} ) { $version = $Config{ZM_DYN_DB_VERSION}; } else { print( STDERR "Please give a valid option\n" ); pod2usage(-exitstatus => -1); } } if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) > 1 ) { print( STDERR "Please give only one option\n" ); pod2usage(-exitstatus => -1); } if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); my $currVersion = $Config{ZM_DYN_CURR_VERSION}; my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; my $lastCheck = $Config{ZM_DYN_LAST_CHECK}; if ( !$currVersion ) { $currVersion = $Config{ZM_VERSION}; my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( "$currVersion" ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); } while( 1 ) { my $now = time(); if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) { Info( "Checking for updates\n" ); use LWP::UserAgent; my $ua = LWP::UserAgent->new; $ua->agent( "ZoneMinder Update Agent/".ZM_VERSION ); if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { $ua->proxy( 'http', $Config{ZM_UPDATE_CHECK_PROXY} ); } my $req = HTTP::Request->new( GET=>'https://update.zoneminder.com/version.txt' ); my $res = $ua->request($req); if ( $res->is_success ) { $lastVersion = $res->content; chomp($lastVersion); $lastCheck = $now; Info( "Got version: '".$lastVersion."'\n" ); my $lv_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_VERSION'"; my $lv_sth = $dbh->prepare_cached( $lv_sql ) or die( "Can't prepare '$lv_sql': ".$dbh->errstr() ); my $lv_res = $lv_sth->execute( $lastVersion ) or die( "Can't execute: ".$lv_sth->errstr() ); $lv_sth->finish(); my $lc_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_CHECK'"; my $lc_sth = $dbh->prepare_cached( $lc_sql ) or die( "Can't prepare '$lc_sql': ".$dbh->errstr() ); my $lc_res = $lc_sth->execute( $lastCheck ) or die( "Can't execute: ".$lc_sth->errstr() ); $lc_sth->finish(); } else { Error( "Error check failed: '".$res->status_line()."'\n" ); } } sleep( 3600 ); } print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); } if ( $rename ) { require File::Find; chdir( EVENT_PATH ); sub renameImage { my $file = $_; # Ignore directories if ( -d $file ) { print( "Checking directory '$file'\n" ); return; } if ( $file !~ /(capture|analyse)-(\d+)(\.jpg)/ ) { return; } my $newFile = "$2-$1$3"; print( "Renaming '$file' to '$newFile'\n" ); rename( $file, $newFile ) or warn( "Can't rename '$file' to '$newFile'" ); } File::Find::find( \&renameImage, '.' ); } if ( $zoneFix ) { my $sql = "select Z.*, M.Width as MonitorWidth, M.Height as MonitorHeight from Zones as Z inner join Monitors as M on Z.MonitorId = M.Id where Z.Units = 'Percent'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my @zones; while( my $zone = $sth->fetchrow_hashref() ) { push( @zones, $zone ); } $sth->finish(); $sql = 'update Zones set MinAlarmPixels = ?, MaxAlarmPixels = ?, MinFilterPixels = ?, MaxFilterPixels = ?, MinBlobPixels = ?, MaxBlobPixels = ? where Id = ?'; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); foreach my $zone ( @zones ) { my $zone_width = (($zone->{HiX}*$zone->{MonitorWidth})-($zone->{LoX}*$zone->{MonitorWidth}))/100; my $zone_height = (($zone->{HiY}*$zone->{MonitorHeight})-($zone->{LoY}*$zone->{MonitorHeight}))/100; my $zone_area = $zone_width * $zone_height; my $monitor_area = $zone->{MonitorWidth} * $zone->{MonitorHeight}; my $res = $sth->execute( ($zone->{MinAlarmPixels}*$monitor_area)/$zone_area, ($zone->{MaxAlarmPixels}*$monitor_area)/$zone_area, ($zone->{MinFilterPixels}*$monitor_area)/$zone_area, ($zone->{MaxFilterPixels}*$monitor_area)/$zone_area, ($zone->{MinBlobPixels}*$monitor_area)/$zone_area, ($zone->{MaxBlobPixels}*$monitor_area)/$zone_area, $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } $sth->finish(); } if ( $migrateEvents ) { my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; my $webGid = (getgrnam( $Config{ZM_WEB_USER} ))[2]; if ( !(($> == 0) || ($> == $webUid)) ) { print( "Error, migrating events can only be done as user root or ".$Config{ZM_WEB_USER}.".\n" ); exit( -1 ); } # Run as web user/group $( = $webGid; $) = $webGid; $< = $webUid; $> = $webUid; print( "\nAbout to convert saved events to deep storage, please ensure that ZoneMinder is fully stopped before proceeding.\nThis process is not easily reversible. Are you sure you wish to proceed?\n\nPress 'y' to continue or 'n' to abort : " ); my $response = ; chomp( $response ); while ( $response !~ /^[yYnN]$/ ) { print( "Please press 'y' to continue or 'n' to abort only : " ); $response = ; chomp( $response ); } if ( $response =~ /^[yY]$/ ) { print( "Converting all events to deep storage.\n" ); chdir( $Config{ZM_PATH_WEB} ); my $sql = "select *, unix_timestamp(StartTime) as UnixStartTime from Events"; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute(); if ( !$res ) { Fatal( "Can't fetch Events: ".$sth->errstr() ); } while( my $event = $sth->fetchrow_hashref() ) { my $oldEventPath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.$event->{Id}; if ( !-d $oldEventPath ) { print( "Warning, can't find old event path '$oldEventPath', already converted?\n" ); next; } print( "Converting event ".$event->{Id}."\n" ); my $newDatePath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.strftime( "%y/%m/%d", localtime($event->{UnixStartTime}) ); my $newTimePath = strftime( "%H/%M/%S", localtime($event->{UnixStartTime}) ); my $newEventPath = $newDatePath.'/'.$newTimePath; ( my $truncEventPath = $newEventPath ) =~ s|/\d+$||; makePath( $truncEventPath, $Config{ZM_PATH_WEB} ); my $idLink = $newDatePath.'/.'.$event->{Id}; symlink( $newTimePath, $idLink ) or die( "Can't symlink $newTimePath -> $idLink: $!" ); rename( $oldEventPath, $newEventPath ) or die( "Can't move $oldEventPath -> $newEventPath: $!" ); } $sth->finish(); print( "Updating configuration.\n" ); $sql = "update Config set Value = ? where Name = 'ZM_USE_DEEP_STORAGE'"; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute( 1 ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); print( "All events converted.\n\n" ); } else { print( "Aborting event conversion.\n\n" ); } } if ( $freshen ) { print( "\nFreshening configuration in database\n" ); migratePaths(); ZoneMinder::Config::loadConfigFromDB(); ZoneMinder::Config::saveConfigToDB(); } # Don't do innoDB upgrade if not interactive if ( $interactive ) { # Now check for MyISAM Tables my @MyISAM_Tables; my $sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='zm' AND engine = 'MyISAM'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); while( my $dbTable = $sth->fetchrow() ) { push @MyISAM_Tables, $dbTable; } $sth->finish(); if ( @MyISAM_Tables ) { print( "\nPrevious versions of ZoneMinder used the MyISAM database engine.\nHowever, the recommended database engine is InnoDB.\n"); print( "\nHint: InnoDB tables are much less likely to be corrupted during an unclean shutdown.\n\nPress 'y' to convert your tables to InnoDB or 'n' to skip : "); my $response = ; chomp( $response ); if ( $response =~ /^[yY]$/ ) { $dbh->do(q|SET sql_mode='traditional'|); # Elevate warnings to errors print "\nConverting MyISAM tables to InnoDB. Please wait.\n"; foreach (@MyISAM_Tables) { my $sql = "ALTER TABLE $_ ENGINE = InnoDB"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); } $dbh->do(q|SET sql_mode=''|); # Set mode back to default } } } # end if interactive if ( $version ) { my ( $detaint_version ) = $version =~ /^([\w.]+)$/; $version = $detaint_version; if ( ZM_VERSION eq $version ) { print( "\nDatabase already at version $version, update aborted.\n\n" ); exit( 0 ); } print( "\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n" ); if ( $interactive ) { if ( $Config{ZM_DYN_DB_VERSION} && $Config{ZM_DYN_DB_VERSION} ne $version ) { print( "\nWARNING - You have specified an upgrade from version $version but the database version found is ".$Config{ZM_DYN_DB_VERSION}.". Is this correct?\nPress enter to continue or ctrl-C to abort : " ); my $response = ; } print( "\nPlease ensure that ZoneMinder is stopped on your system prior to upgrading the database.\nPress enter to continue or ctrl-C to stop : " ); my $response = ; print( "\nDo you wish to take a backup of your database prior to upgrading?\nThis may result in a large file in @ZM_TMPDIR@ if you have a lot of events.\nPress 'y' for a backup or 'n' to continue : " ); $response = ; chomp( $response ); while ( $response !~ /^[yYnN]$/ ) { print( "Please press 'y' for a backup or 'n' to continue only : " ); $response = ; chomp( $response ); } if ( $response =~ /^[yY]$/ ) { my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $command = 'mysqldump'; if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { $command .= " -S".$portOrSocket; } else { $command .= " -h".$host." -P".$portOrSocket; } } else { $command .= " -h".$host; } if ( $dbUser ) { $command .= ' -u'.$dbUser; $command .= ' -p"'.$dbPass.'"' if $dbPass; } my $backup = "@ZM_TMPDIR@/".$Config{ZM_DB_NAME}."-".$version.".dump"; $command .= " --add-drop-table --databases ".$Config{ZM_DB_NAME}." > ".$backup; print( "Creating backup to $backup. This may take several minutes.\n" ); print( "Executing '$command'\n" ) if ( logDebugging() ); my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { chomp( $output ); print( "Output: $output\n" ); } if ( $status ) { die( "Command '$command' exited with status: $status\n" ); } else { print( "Database successfully backed up to $backup, proceeding to upgrade.\n" ); } } elsif ( $response !~ /^[nN]$/ ) { die( "Unexpected response '$response'" ); } } print( "\nUpgrading database to version ".ZM_VERSION."\n" ); # Update config first of all migratePaths(); ZoneMinder::Config::loadConfigFromDB(); ZoneMinder::Config::saveConfigToDB(); my $cascade = undef; if ( $cascade || $version eq "1.19.0" ) { # Patch the database patchDB( $dbh, "1.19.0" ); $cascade = !undef; } if ( $cascade || $version eq "1.19.1" ) { # Patch the database patchDB( $dbh, "1.19.1"); $cascade = !undef; } if ( $cascade || $version eq "1.19.2" ) { # Patch the database patchDB( $dbh, "1.19.2" ); $cascade = !undef; } if ( $cascade || $version eq "1.19.3" ) { # Patch the database patchDB( $dbh, "1.19.3" ); $cascade = !undef; } if ( $cascade || $version eq "1.19.4" ) { # Rename the event directories and create a new symlink for the names chdir( EVENT_PATH ); my $sql = "select * from Monitors order by Id"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { if ( -d $monitor->{Name} ) { rename( $monitor->{Name}, $monitor->{Id} ) or warn( "Can't rename existing monitor directory '$monitor->{Name}' to '$monitor->{Id}': $!" ); symlink( $monitor->{Id}, $monitor->{Name} ) or warn( "Can't symlink monitor directory '$monitor->{Id}' to '$monitor->{Name}': $!" ); } } $sth->finish(); # Patch the database patchDB( $dbh, "1.19.4" ); $cascade = !undef; } if ( $cascade || $version eq "1.19.5" ) { print( "\nThis version now only uses one database user.\nPlease ensure you have run zmconfig.pl and re-entered your database username and password prior to upgrading, or the upgrade will fail.\nPress enter to continue or ctrl-C to stop : " ); # Patch the database my $dummy = ; patchDB( $dbh, "1.19.5" ); $cascade = !undef; } if ( $cascade || $version eq "1.20.0" ) { # Patch the database patchDB( $dbh, "1.20.0" ); $cascade = !undef; } if ( $cascade || $version eq "1.20.1" ) { # Patch the database patchDB( $dbh, "1.20.1" ); $cascade = !undef; } if ( $cascade || $version eq "1.21.0" ) { # Patch the database patchDB( $dbh, "1.21.0" ); $cascade = !undef; } if ( $cascade || $version eq "1.21.1" ) { # Patch the database patchDB( $dbh, "1.21.1" ); $cascade = !undef; } if ( $cascade || $version eq "1.21.2" ) { # Patch the database patchDB( $dbh, "1.21.2" ); $cascade = !undef; } if ( $cascade || $version eq "1.21.3" ) { # Patch the database patchDB( $dbh, "1.21.3" ); # Add appropriate widths and heights to events { print( "Updating events. This may take a few minutes. Please wait.\n" ); my $sql = "select * from Monitors order by Id"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { my $sql = 'update Events set Width = ?, Height = ? where MonitorId = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $monitor->{Width}, $monitor->{Height}, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } $sth->finish(); } # Add sequence numbers { print( "Updating monitor sequences. Please wait.\n" ); my $sql = "select * from Monitors order by Id"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my $sequence = 1; while( my $monitor = $sth->fetchrow_hashref() ) { my $sql = 'update Monitors set Sequence = ? where Id = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $sequence++, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } $sth->finish(); } # Update saved filters { print( "Updating saved filters. Please wait.\n" ); my $sql = "select * from Filters"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my @filters; while( my $filter = $sth->fetchrow_hashref() ) { push( @filters, $filter ); } $sth->finish(); $sql = 'update Filters set Query = ? where Name = ?'; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); foreach my $filter ( @filters ) { if ( $filter->{Query} =~ /op\d=&/ ) { ( my $newQuery = $filter->{Query} ) =~ s/(op\d=)&/$1=&/g; $res = $sth->execute( $newQuery, $filter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); } } } $cascade = !undef; } if ( $cascade || $version eq "1.21.4" ) { # Patch the database patchDB( $dbh, "1.21.4" ); # Convert zones to new format { print( "Updating zones. Please wait.\n" ); # Get the existing zones from the DB my $sql = "select Z.*,M.Width,M.Height from Zones as Z inner join Monitors as M on (Z.MonitorId = M.Id)"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my @zones; while( my $zone = $sth->fetchrow_hashref() ) { push( @zones, $zone ); } $sth->finish(); no strict 'refs'; foreach my $zone ( @zones ) { # Create the coordinate strings if ( $zone->{Units} eq 'Pixels' ) { my $sql = "update Zones set NumCoords = 4, Coords = concat( LoX,',',LoY,' ',HiX,',',LoY,' ',HiX,',',HiY,' ',LoX,',',HiY ), Area = round( ((HiX-LoX)+1)*((HiY-LoY)+1) ) where Id = ?"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } else { my $loX = ($zone->{LoX} * ($zone->{Width}-1) ) / 100; my $hiX = ($zone->{HiX} * ($zone->{Width}-1) ) / 100; my $loY = ($zone->{LoY} * ($zone->{Height}-1) ) / 100; my $hiY = ($zone->{HiY} * ($zone->{Height}-1) ) / 100; my $area = (($hiX-$loX)+1)*(($hiY-$loY)+1); my $sql = "update Zones set NumCoords = 4, Coords = concat( round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?) ), Area = round(?), MinAlarmPixels = round(?), MaxAlarmPixels = round(?), MinFilterPixels = round(?), MaxFilterPixels = round(?), MinBlobPixels = round(?), MaxBlobPixels = round(?) where Id = ?"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $loX, $loY, $hiX, $loY, $hiX, $hiY, $loX, $hiY, $area, ($zone->{MinAlarmPixels}*$area)/100, ($zone->{MaxAlarmPixels}*$area)/100, ($zone->{MinFilterPixels}*$area)/100, ($zone->{MaxFilterPixels}*$area)/100, ($zone->{MinBlobPixels}*$area)/100, ($zone->{MaxBlobPixels}*$area)/100, $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } } } # Convert run states to new format { print( "Updating run states. Please wait.\n" ); # Get the existing zones from the DB my $sql = "select * from States"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my @states; while( my $state = $sth->fetchrow_hashref() ) { push( @states, $state ); } $sth->finish(); foreach my $state ( @states ) { my @new_defns; foreach my $defn ( split( /,/, $state->{Definition} ) ) { push( @new_defns, $defn.":1" ); } my $sql = 'update States set Definition = ? where Name = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( join( ',', @new_defns ), $state->{Name} ) or die( "Can't execute: ".$sth->errstr() ); } } $cascade = !undef; } if ( $cascade || $version eq "1.22.0" ) { # Patch the database patchDB( $dbh, "1.22.0" ); # Check for maximum FPS setting and update alarm max fps settings { print( "Updating monitors. Please wait.\n" ); if ( defined(&ZM_NO_MAX_FPS_ON_ALARM) && &ZM_NO_MAX_FPS_ON_ALARM ) { # Update the individual monitor settings to match the previous global one my $sql = "update Monitors set AlarmMaxFPS = NULL"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); } else { # Update the individual monitor settings to match the previous global one my $sql = "update Monitors set AlarmMaxFPS = MaxFPS"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); } } { print( "Updating mail configuration. Please wait.\n" ); my ( $sql, $sth, $res ); if ( defined(&ZM_EMAIL_TEXT) && &ZM_EMAIL_TEXT ) { my ( $email_subject, $email_body ) = $Config{ZM_EMAIL_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_SUBJECT', Value = '".$email_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the email used to send matching event details', Help = 'This option is used to define the subject of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_BODY', Value = '".$email_body."', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the email used to send matching event details', Help = 'This option is used to define the content of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); } if ( defined(&ZM_MESSAGE_TEXT) && &ZM_MESSAGE_TEXT ) { my ( $message_subject, $message_body ) = $Config{ZM_MESSAGE_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_SUBJECT', Value = '".$message_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI%', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the message used to send matching event details', Help = 'This option is used to define the subject of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_BODY', Value = '".$message_body."', Type = 'text', DefaultValue = 'ZM alarm detected - %ED% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the message used to send matching event details', Help = 'This option is used to define the content of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); } } $cascade = !undef; } if ( $cascade || $version eq "1.22.1" ) { # Patch the database patchDB( $dbh, "1.22.1" ); $cascade = !undef; } if ( $cascade || $version eq "1.22.2" ) { # Patch the database patchDB( $dbh, "1.22.2" ); $cascade = !undef; } if ( $cascade || $version eq "1.22.3" ) { # Patch the database patchDB( $dbh, "1.22.3" ); # Convert timestamp strings to new format { my $sql = "select * from Monitors"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my @db_monitors; while( my $db_monitor = $sth->fetchrow_hashref() ) { push( @db_monitors, $db_monitor ); } $sth->finish(); foreach my $db_monitor ( @db_monitors ) { if ( $db_monitor->{LabelFormat} =~ /\%\%s/ ) { $db_monitor->{LabelFormat} =~ s/\%\%s/%N/; $db_monitor->{LabelFormat} =~ s/\%\%s/%Q/; my $sql = 'update Monitors set LabelFormat = ? where Id = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $db_monitor->{LabelFormat}, $db_monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } } } # Convert filters to new format { my $sql = "select * from Filters"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my @dbFilters; while( my $dbFilter = $sth->fetchrow_hashref() ) { push( @dbFilters, $dbFilter ); } $sth->finish(); foreach my $dbFilter ( @dbFilters ) { my %filter_terms; foreach my $filter_parm ( split( /&/, $dbFilter->{Query} ) ) { my( $key, $value ) = split( /=/, $filter_parm, 2 ); if ( $key ) { $filter_terms{$key} = $value; } } my $filter = { 'terms' => [] }; for ( my $i = 1; $i <= $filter_terms{trms}; $i++ ) { my $term = {}; my $conjunction_name = "cnj$i"; my $obracket_name = "obr$i"; my $cbracket_name = "cbr$i"; my $attr_name = "attr$i"; my $op_name = "op$i"; my $value_name = "val$i"; $term->{cnj} = $filter_terms{$conjunction_name} if ( $filter_terms{$conjunction_name} ); $term->{obr} = $filter_terms{$obracket_name} if ( $filter_terms{$obracket_name} ); $term->{attr} = $filter_terms{$attr_name} if ( $filter_terms{$attr_name} ); $term->{val} = $filter_terms{$value_name} if ( defined($filter_terms{$value_name}) ); $term->{op} = $filter_terms{$op_name} if ( $filter_terms{$op_name} ); $term->{cbr} = $filter_terms{$cbracket_name} if ( $filter_terms{$cbracket_name} ); push( @{$filter->{terms}}, $term ); } $filter->{sort_field} = $filter_terms{sort_field} if ( $filter_terms{sort_field} ); $filter->{sort_asc} = $filter_terms{sort_asc} if ( $filter_terms{sort_asc} ); $filter->{limit} = $filter_terms{limit} if ( $filter_terms{limit} ); my $newQuery = 'a:'.int(keys(%$filter)).':{s:5:"terms";a:'.int(@{$filter->{terms}}).':{'; my $i = 0; foreach my $term ( @{$filter->{terms}} ) { $newQuery .= 'i:'.$i.';a:'.int(keys(%$term)).':{'; while ( my ( $key, $val ) = each( %$term ) ) { $newQuery .= 's:'.length($key).':"'.$key.'";'; $newQuery .= 's:'.length($val).':"'.$val.'";'; } $newQuery .= '}'; $i++; } $newQuery .= '}'; foreach my $field ( 'sort_field', 'sort_asc', 'limit' ) { if ( defined($filter->{$field}) ) { $newQuery .= 's:'.length($field).':"'.$field.'";'; $newQuery .= 's:'.length($filter->{$field}).':"'.$filter->{$field}.'";'; } } $newQuery .= '}'; my $sql = 'update Filters set Query = ? where Name = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $newQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); } } # Update the stream quality setting to the old image quality ones { my $dbh = zmDbConnect(); my $sql = "update Config set Value = ? where Name = 'ZM_JPEG_STREAM_QUALITY'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $Config{ZM_JPEG_IMAGE_QUALITY} ) or die( "Can't execute: ".$sth->errstr() ); } $cascade = !undef; } if ( $cascade || $version eq "1.23.0" ) { # Patch the database patchDB( $dbh, "1.23.0" ); $cascade = !undef; } if ( $cascade || $version eq "1.23.1" ) { # Patch the database patchDB( $dbh, "1.23.1" ); $cascade = !undef; } if ( $cascade || $version eq "1.23.2" ) { # Patch the database patchDB( $dbh, "1.23.2" ); $cascade = !undef; } if ( $cascade || $version eq "1.23.3" ) { # Patch the database patchDB( $dbh, "1.23.3" ); $cascade = !undef; } if ( $cascade || $version eq "1.24.0" ) { # Patch the database patchDB( $dbh, "1.24.0" ); $cascade = !undef; } if ( $cascade || $version eq "1.24.1" ) { # Patch the database patchDB( $dbh, "1.24.1" ); $cascade = !undef; } if ( $cascade || $version eq "1.24.2" ) { # Patch the database patchDB( $dbh, "1.24.2" ); $cascade = !undef; } if ( $cascade || $version eq "1.24.3" ) { my $result = eval { require PHP::Serialization; PHP::Serialization->import(); }; die( "Unable to perform upgrade from 1.24.3, PHP::Serialization module not found" ) if ( $result ); # Patch the database patchDB( $dbh, "1.24.3" ); # Convert filters to JSON from PHP format serialisation { print( "\nConverting filters from PHP to JSON format\n" ); my $sql = "select * from Filters"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); my @dbFilters; while( my $dbFilter = $sth->fetchrow_hashref() ) { push( @dbFilters, $dbFilter ); } $sth->finish(); foreach my $dbFilter ( @dbFilters ) { print( " ".$dbFilter->{Name} ); eval { my $phpQuery = $dbFilter->{Query}; my $query = PHP::Serialization::unserialize( $phpQuery ); my $jsonQuery = jsonEncode( $query ); my $sql = 'update Filters set Query = ? where Name = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $jsonQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); }; if ( $@ ) { print( " - failed, please check or report. Query is '".$dbFilter->{Query}."'\n" ); print( $@ ); } else { print( " - complete\n" ); } } print( "Conversion complete\n" ); } $cascade = !undef; } if ( $cascade || $version eq "1.24.4" ) { # Patch the database patchDB( $dbh, "1.24.4" ); # Copy the FTP specific values to the new general config my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; my $fetchSth = $dbh->prepare_cached( $fetchSql ) or die( "Can't prepare '$fetchSql': ".$dbh->errstr() ); my $updateSql = 'update Config set Value = ? where Name = ?'; my $updateSth = $dbh->prepare_cached( $updateSql ) or die( "Can't prepare '$updateSql': ".$dbh->errstr() ); my $fetchRes = $fetchSth->execute() or die( "Can't execute: ".$fetchSth->errstr() ); while( my $config = $fetchSth->fetchrow_hashref() ) { ( my $name = $config->{Name} ) =~ s/_FTP_/_/; my $updateRes = $updateSth->execute( $config->{Value}, $name ) or die( "Can't execute: ".$updateSth->errstr() ); } $cascade = !undef; } if ( $cascade || $version lt "1.26.0" ) { my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); die "Error: " . $dbh->errstr . "\n" unless ($sth); die "Error: " . $sth->errstr . "\n" unless ($sth->execute); my $columns = $sth->{'NAME'}; if ( ! grep(/^Colours$/, @$columns ) ) { $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); } # end if if ( ! grep(/^Deinterlacing$/, @$columns ) ) { $dbh->do(q{alter table Monitors add column `Deinterlacing` INT unsigned NOT NULL default '0' after `Orientation`;}); } # end if $sth->finish(); $cascade = !undef; $version = '1.26.0'; } if ( $version ge '1.26.0' ) { my @files; $updateDir = $Config{ZM_PATH_DATA}.'/db' if ! $updateDir; opendir( my $dh, $updateDir ) || die "Can't open updateDir $!"; #@files = sort grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); #PP - use perl version sort @files = sort { my ($x) = ($a =~ m/^zm_update\-(.*)\.sql$/); my ($y) = ($b =~ m/^zm_update\-(.*)\.sql$/); version->parse($x) <=> version->parse($y) } grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); closedir $dh; if ( ! @files ) { die "Should have found upgrade scripts at $updateDir\n"; } # end if foreach my $patch ( @files ) { my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; #PP make sure we use version compare if ( version->parse('v' . $v) > version->parse('v' . $version) ) { print( "Upgrading DB to $v from $version\n" ); patchDB( $dbh, $v ); my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); #patchDB_using_do( $dbh, $version, $updateDir.'/'.$patch ); } # end if newer version } # end foreach patchfile $cascade = !undef; } # end if if ( $cascade ) { my $installed_version = ZM_VERSION; my $sql = 'update Config set Value = ? where Name = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( "$installed_version", 'ZM_DYN_DB_VERSION' ) or die( "Can't execute: ".$sth->errstr() ); $res = $sth->execute( "$installed_version", 'ZM_DYN_CURR_VERSION' ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); } else { zmDbDisconnect(); die( "Can't find upgrade from version '$version'" ); } # Re-enable the privacy popup after each upgrade my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); } zmDbDisconnect(); exit( 0 ); sub patchDB_using_do { my ( $dbh, $version, $file ) = @_; open( my $fh, '<', $file ) or die "Unable to open $file $!"; $/ = undef; my $sql = <$fh>; close $fh; if ( $sql ) { $dbh->{'AutoCommit'} = 0; $dbh->do($sql); if ( $dbh->errstr() ) { $dbh->rollback(); die "Error: " . $dbh->errstr(). ". Rolled back.\n"; } # end if error my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); $dbh->{'AutoCommit'} = 1; } else { Warning("Empty db update file at $file"); } } sub patchDB { my $dbh = shift; my $version = shift; my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $command = 'mysql'; if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { $command .= ' -S'.$portOrSocket; } else { $command .= ' -h'.$host.' -P'.$portOrSocket; } } else { $command .= ' -h'.$host; } if ( $dbUser ) { $command .= ' -u'.$dbUser; $command .= ' -p"'.$dbPass.'"' if $dbPass; } $command .= ' '.$Config{ZM_DB_NAME}.' < '; if ( $updateDir ) { $command .= $updateDir; } else { $command .= $Config{ZM_PATH_DATA}.'/db'; } $command .= '/zm_update-'.$version.'.sql'; print( "Executing '$command'\n" ) if ( logDebugging() ); my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { chomp( $output ); print( "Output: $output\n" ); } if ( $status ) { die( "Command '$command' exited with status: $status\n" ); } print( "\nDatabase successfully upgraded to version $version.\n" ); } sub migratePaths { my $customConfigFile = '@ZM_CONFIG_SUBDIR@/zmcustom.conf'; if ( (! -e $customConfigFile ) && ( ZM_VERSION ge '1.31.0' ) && ($Config{ZM_DYN_DB_VERSION} lt '1.31.1') ) { my %customConfig; # Check the traditional default values for the variables previsouly found under Options -> Paths if ( $Config{ZM_DIR_EVENTS} ne 'events' ) { $customConfig{ZM_DIR_EVENTS} = $Config{ZM_DIR_EVENTS}; } if ( $Config{ZM_DIR_IMAGES} ne 'images' ) { $customConfig{ZM_DIR_IMAGES} = $Config{ZM_DIR_IMAGES}; } if ( $Config{ZM_DIR_SOUNDS} ne 'sounds' ) { $customConfig{ZM_DIR_SOUNDS} = $Config{ZM_DIR_SOUNDS}; } if ( $Config{ZM_PATH_ZMS} ne '@ZM_PATH_ZMS@' ) { $customConfig{ZM_PATH_ZMS} = $Config{ZM_PATH_ZMS}; } if ( $Config{ZM_PATH_MAP} ne '/dev/shm' ) { $customConfig{ZM_PATH_MAP} = $Config{ZM_PATH_MAP}; } if ( $Config{ZM_PATH_SOCKS} ne '@ZM_SOCKDIR@' ) { $customConfig{ZM_PATH_SOCKS} = $Config{ZM_PATH_SOCKS}; } if ( $Config{ZM_PATH_LOGS} ne '@ZM_LOGDIR@' ) { $customConfig{ZM_PATH_LOGS} = $Config{ZM_PATH_LOGS}; } if ( $Config{ZM_PATH_SWAP} ne '@ZM_TMPDIR@' ) { $customConfig{ZM_PATH_SWAP} = $Config{ZM_PATH_SWAP}; } if ( $Config{ZM_PATH_ARP} ne '' ) { $customConfig{ZM_PATH_ARP} = $Config{ZM_PATH_ARP}; } # If any variables differ from their expected default value, # save them to a config file before they get purged from the database if ( %customConfig ) { print("\nMigrating custom config values from Options -> Paths\nto $customConfigFile.\n"); print("\nPlease verify these values before starting ZoneMinder.\n\n"); open(my $fh, '>', $customConfigFile) or die "Could open $customConfigFile for writing: $!."; print $fh "# These values were autogenerated by zmupdate.pl\n"; print $fh "# You may edit these values. ZoneMinder will not overwrite them.\n"; print $fh "#\n\n"; while (my ($key, $value) = each %customConfig) { print $fh "$key=$value\n"; } close $fh; my $gid = getgrnam('@ZM_WEB_GROUP@'); chown -1, $gid, $customConfigFile; chmod 0640, $customConfigFile; } } } 1; __END__ ZoneMinder-1.32.2/scripts/zmdbrestore.in0000644000000000000000000001137313365153155016742 0ustar rootroot#!/bin/bash #=============================================================================== # # FILE: zmdbrestore # # USAGE: ./zmdbrestore # # DESCRIPTION: Restore a ZoneMinder DB from a backup created by zm_db_backup # # OPTIONS: --- # REQUIREMENTS: --- # BUGS: --- # NOTES: --- # AUTHOR: (), # COMPANY: # VERSION: 1.0 # CREATED: 05/29/2006 04:45:06 PM PDT # REVISION: --- #=============================================================================== ZM_CONFIG=@ZM_CONFIG@ ZM_BACKUP=/var/lib/zm/zm_backup.sql EVENTS_DIR=events loadcfg() { if [ -f $ZM_CONFIG ]; then . $ZM_CONFIG else echo "ERROR: $ZM_CONFIG not found." exit 1 fi } chkcfg(){ for n in ZM_DB_HOST ZM_DB_NAME ZM_DB_USER ZM_DB_PASS; do eval "val=\$$n" if [ "$val" = "" ]; then echo "ERROR($ZM_CONFIG): $n should not be empty." echo "Enter a $n for ZM to use the Database." if [ "$n" = "ZM_DB_PASS" ]; then echo -n "Will not echo on screen $n : " stty -echo # Turns off screen echo. read newval stty echo # Restores screen echo. echo "" ### The following can be used to generate a random password # randstr newval 16 else echo -n "$n : " read newval fi cp $ZM_CONFIG /tmp/$$ && sed 's/^'$n='.*$/'$n=$newval'/g' /tmp/$$ >$ZM_CONFIG fi done if [ "$ZM_DB_HOST" = "localhost" ] then ClientHost=localhost else ClientHost=`hostname` fi } reloadcfg(){ loadcfg } chk_backup_ver(){ if [ -e $ZM_BACKUP ] then BACKUP_VER=$(cat $ZM_BACKUP | head -n 2 |tail -n 1 |cut -f 8 -d " ") else echo "$ZM_BACKUP doesn't exist" exit 1 fi if [ $BACKUP_VER != $ZM_VERSION ] then echo "$ZM_BACKUP is from version $BACKUP_VER" echo "ZoneMinder version is $ZM_VERSION" exit 1 fi } getmylogin(){ echo "Enter MySQL Administrator username" echo "(Default: root and password is blank)" echo -n "MySQL Admin: " read MYADMIN echo -n "Password: " read MYPASS if [ "X$MYPASS" != "X" ]; then MYPASS="-p$MYPASS"; fi echo "\q" |mysql -u $MYADMIN $MYPASS || exit 0 } checkfordb(){ if echo "show databases" |mysql -u $MYADMIN "$MYPASS" |grep zm then echo "A $ZM_DB_NAME database exists." while [ true ] do echo "Choose one of the following options:" echo "[D]rop the old database and reinitialize" echo "[E]xit and do nothing" read OPTION case $OPTION in "D"|"d") echo "drop database zm;"|mysql -u $MYADMIN $MYPASS return ;; "E"|"e") exit 0 ;; esac done fi } initdb(){ sql=/tmp/zm.crdb.sql echo "" >$sql chmod 600 $sql echo "CREATE DATABASE $ZM_DB_NAME;" >>$sql echo "USE $ZM_DB_NAME;" >>$sql echo "GRANT all on $ZM_DB_NAME.* TO '$ZM_DB_USER'@'$ClientHost' IDENTIFIED BY '$ZM_DB_PASS';" >>$sql cat $sql | mysql -B -h $ZM_DB_HOST -u $MYADMIN $MYPASS rm -f $sql cat $ZM_PATH_UPDATE/zm_create.sql | mysql -h $ZM_DB_HOST -u $ZM_DB_USER -p$ZM_DB_PASS $ZM_DB_NAME } restoredb(){ if [ -e $ZM_BACKUP ] then cat $ZM_BACKUP | mysql -h $ZM_DB_HOST -u $ZM_DB_USER -p$ZM_DB_PASS $ZM_DB_NAME else echo "$ZM_BACKUP doesn't exist" exit 1 fi } restore_events(){ for SQL in $(find $ZM_PATH_WEB/$EVENTS_DIR -name .sql) do cat $SQL | mysql -h $ZM_DB_HOST -u $ZM_DB_USER -p$ZM_DB_PASS $ZM_DB_NAME done } loadcfg chkcfg reloadcfg chk_backup_ver getmylogin checkfordb initdb restoredb restore_events exit 0 ZoneMinder-1.32.2/scripts/zmvideo.pl.in0000644000000000000000000001776413365153155016503 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder Video Creation Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmvideo.pl - ZoneMinder Video Creation Script =head1 SYNOPSIS zmvideo.pl [ -e ,--event= | --filter_name= | --filter_id= ] [--concat=filename] [--format ] [--rate=] [--scale=] [--fps=] [--size=] [--overwrite] =head1 DESCRIPTION This script is used to create MPEG videos of events for the web pages or as email attachments. =head1 OPTIONS -c[=filename], --concat[=filename] - When creating videos for multiple events, create a concatenated video as well. - If not specified, filename is taken from filter name. -e, --event= - What event to create the video for --filter_name= - The name of a saved filter to generate a video for all events returned by it. --filter_id= - The id of a saved filter to generate a video for all events returned by it. -f, --format= - What format to create the video in, default is mpg. For ffmpeg only. -r, --rate= - Relative rate, 1 = realtime, 2 = double speed, 0.5 = half speed etc. -s, --scale= - Scale, 1 = normal, 2 = double size, 0.5 = half size etc. -F, --fps= - Absolute frame rate, in frames per second -S, --size= - Absolute video size, WxH or other specification supported by ffmpeg -o, --overwrite - Whether to overwrite an existing file, off by default. -v, --version - Outputs the currently installed version of ZoneMinder =cut use strict; use bytes; # ========================================================================== # # You shouldn't need to change anything from here downwards # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; require ZoneMinder::Filter; require ZoneMinder::Event; use DBI; use autouse 'Data::Dumper'=>qw(Dumper); use POSIX qw(strftime); use Getopt::Long qw(:config no_ignore_case ); use Cwd; use autouse 'Pod::Usage'=>qw(pod2usage); $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); my $event_id; my $concat_name; my $filter_name; my $filter_id; my $format = 'mpg'; my $rate = ''; my $scale = ''; my $fps = ''; my $size = ''; my $overwrite = 0; my $version = 0; GetOptions( 'concat|c:s' =>\$concat_name, 'event|e=i' =>\$event_id, 'filter_name=s' =>\$filter_name, 'filter_id=i' =>\$filter_id, 'format|f=s' =>\$format, 'rate|r=f' =>\$rate, 'scale|s=f' =>\$scale, 'fps|F=f' =>\$fps, 'size|S=s' =>\$size, 'overwrite' =>\$overwrite, 'version' =>\$version ) or pod2usage(-exitstatus => -1); if ( $version ) { print ZoneMinder::Base::ZM_VERSION . "\n"; exit(0); } if ( !( $filter_id or $filter_name or $event_id ) || ($event_id and ( $event_id < 0 ) ) ) { print( STDERR "Please give a valid event id or filter name\n" ); pod2usage(-exitstatus => -1); } if ( ! $Config{ZM_OPT_FFMPEG} ) { print( STDERR "Mpeg encoding is not currently enabled\n" ); exit(-1); } my @formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); for ( my $i = 0; $i < @formats; $i++ ) { if ( $i =~ /^(.+)\*$/ ) { $format = $formats[$i] = $1; } } if ( !$rate && !$fps ) { $rate = 1; } if ( !$scale && !$size ) { $scale = 1; } if ( $rate && ($rate < 0.25 || $rate > 100) ) { print( STDERR "Rate is out of range, 0.25 >= rate <= 100\n" ); pod2usage(-exitstatus => -1); } if ( $scale && ($scale < 0.25 || $scale > 4) ) { print( STDERR "Scale is out of range, 0.25 >= scale <= 4\n" ); pod2usage(-exitstatus => -1); } if ( $fps && ($fps > 30) ) { print( STDERR "FPS is out of range, <= 30\n" ); pod2usage(-exitstatus => -1); } my ( $detaint_format ) = $format =~ /^(\w+)$/; my ( $detaint_rate ) = $rate =~ /^(-?\d+(?:\.\d+)?)$/; my ( $detaint_scale ) = $scale =~ /^(-?\d+(?:\.\d+)?)$/; my ( $detaint_fps ) = $fps =~ /^(-?\d+(?:\.\d+)?)$/; my ( $detaint_size ) = $size =~ /^(\w+)$/; $format = $detaint_format; $rate = $detaint_rate; $scale = $detaint_scale; $fps = $detaint_fps; $size = $detaint_size; my $dbh = zmDbConnect(); my $cwd = getcwd; my $video_name; my @event_ids; # Fail if the path to a valid ffmpeg binary is not set if ( ! -x $Config{ZM_PATH_FFMPEG} ) { Fatal("Ffmpeg binary not found or not executable. Verify ZM_PATH_FFMPEG points to ffmpeg, avconv, or a compatible binary."); } if ( $event_id ) { @event_ids = ( $event_id ); } elsif ( $filter_name or $filter_id ) { my $Filter = ZoneMinder::Filter->find_one( ($filter_name ? ( Name => $filter_name ) : () ), ($filter_id ? ( Id => $filter_name ) : () ), ); if ( ! $Filter ) { Fatal("Filter $filter_name $filter_id not found."); } @event_ids = map { $_->{Id} } $Filter->Execute(); if ( ! @event_ids ) { Fatal( "No events found for $filter_name") } else { Debug(@event_ids . " events found for $filter_name"); } $concat_name = $filter_name if $concat_name eq ''; } my $sql = " SELECT (SELECT max(Delta) FROM Frames WHERE EventId=Events.Id)-(SELECT min(Delta) FROM Frames WHERE EventId=Events.Id) as FullLength, Events.*, unix_timestamp(Events.StartTime) as Time, M.Name as MonitorName, M.Palette FROM Events INNER JOIN Monitors as M on Events.MonitorId = M.Id WHERE Events.Id = ? "; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); Debug($sql); my @video_files; foreach my $event_id ( @event_ids ) { my $res = $sth->execute( $event_id ) or Fatal( "Can't execute: ".$sth->errstr() ); my $event = $sth->fetchrow_hashref(); my $Event = new ZoneMinder::Event( $$event{Id}, $event ); my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format ); if ( $video_file ) { push @video_files, $video_file; print( STDOUT $video_file."\n" ); } else { Warning("No video file generated for event $event_id"); } } # end foreach event_id if ( $concat_name ) { ($cwd) = $cwd =~ /(.*)/; # detaint chdir( $cwd ); ($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/; my $concat_list_file = "/tmp/$concat_name.concat.lst"; my $video_file = $concat_name . '.'. $detaint_format; open( my $fd, '>', $concat_list_file ) or die "Can't open $concat_list_file: $!"; foreach ( @video_files ) { print $fd "file '$_'\n"; } close $fd; my $command = $Config{ZM_PATH_FFMPEG} . " -f concat -safe 0 -i $concat_list_file -c copy " .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} ." '$video_file' > $Config{ZM_PATH_LOGS}/ffmpeg_${concat_name}.log 2>&1" ; Debug( $command."\n" ); my $output = qx($command); my $status = $? >> 8; unlink $concat_list_file; if ( $status ) { Error( "Unable to generate video, check $Config{ZM_PATH_LOGS}/ffmpeg_${concat_name}.log for details"); exit(-1); } print( STDOUT $video_file."\n" ); } exit( 0 ); __END__ ZoneMinder-1.32.2/scripts/zmonvif-probe.pl.in0000755000000000000000000001077613365153155017622 0ustar rootroot#!/usr/bin/perl -w use strict; # # ========================================================================== # # ZoneMinder ONVIF Control Protocol Module # Copyright (C) 2014 Jan M. Hochstein # # 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. # # ========================================================================== # # This module contains the implementation of the ONVIF capability prober # use Getopt::Std; require ONVIF::Client; # ======================================================================== # options processing $Getopt::Std::STANDARD_HELP_VERSION = 1; our ($opt_v); my $OPTIONS = "v"; sub HELP_MESSAGE { my ($fh, $pkg, $ver, $opts) = @_; print $fh "Usage: " . __FILE__ . " [-v] probe \n"; print $fh " " . __FILE__ . " [-v] \n"; print $fh <new( { 'url_svc_device' => $url_svc_device, 'soap_version' => $soap_version } ); $client->set_credentials($username, $password, 1); $client->create_services(); if ( $action eq "profiles" ) { ZoneMinder::ONVIF::profiles($client); } elsif( $action eq "move" ) { my $dir = shift; ZoneMinder::ONVIF::move($client, $dir); } elsif ( $action eq "metadata" ) { ZoneMinder::ONVIF::metadata($client); } else { print("Error: Unknown command\"$action\""); exit(1); } } 1; __END__ =head1 NAME zmonvif-probe.pl - ZoneMinder ONVIF probing tool =head1 SYNOPSIS zmonfig-probe.pl [-v] probe [-v] \n"; Commands are: probe - scan for devices on the local network and list them profiles - print the device's supported stream configurations metadata - print some of the device's configuration settings move - move the device (only ptz cameras) Common parameters: -v - increase verbosity Device access parameters (for all commands but 'probe'): device URL - the ONVIF Device service URL soap version - SOAP version (1.1 or 1.2) user - username of a user with access to the device password - password for the user =head1 DESCRIPTION =head1 OPTIONS -c, --continuous - Run continuously -f, --force - Run even if pid file exists -i, --interactive - Ask before applying any changes -m, --monitor_id - Only consider the given monitor -r, --report - Just report don't actually do anything -s, --storage_id - Specify a storage area to audit instead of all -v, --version - Print the installed version of ZoneMinder =cut ZoneMinder-1.32.2/scripts/zmstats.pl.in0000644000000000000000000000553513365153155016524 0ustar rootroot#!/usr/bin/perl -wT # # ========================================================================== # # ZoneMinder WatchDog Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmwatch.pl - ZoneMinder Stats Updating Script =head1 SYNOPSIS zmstats.pl =head1 DESCRIPTION This does background updating various stats in the db like event counts, diskspace, etc. =cut use strict; use bytes; # ========================================================================== # # These are the elements you can edit to suit your installation # # ========================================================================== use constant START_DELAY => 30; # To give everything else time to start # ========================================================================== # # Don't change anything below here # # ========================================================================== @EXTRA_PERL_LIB@ use ZoneMinder; use POSIX; use DBI; use autouse 'Data::Dumper'=>qw(Dumper); $| = 1; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); Info( "Stats Daemon starting in ".START_DELAY." seconds\n" ); sleep( START_DELAY ); my $dbh = zmDbConnect(); while( 1 ) { while ( ! ( $dbh and $dbh->ping() ) ) { Info("Reconnecting to db"); if ( ! ( $dbh = zmDbConnect() ) ) { #What we do here is not that important, so just skip this interval sleep($Config{ZM_STATS_UPDATE_INTERVAL}); } } $dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr()); $dbh->do('DELETE FROM Events_Day WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr()); $dbh->do('DELETE FROM Events_Week WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr()); $dbh->do('DELETE FROM Events_Month WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr()); sleep($Config{ZM_STATS_UPDATE_INTERVAL}); } # end while (1) Info( "Stats Daemon exiting\n" ); exit(); 1; __END__ ZoneMinder-1.32.2/scripts/zmcamtool.pl.in0000644000000000000000000003070713365153155017023 0ustar rootroot#!/usr/bin/perl -w # # ========================================================================== # # ZoneMinder Update Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # 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. # # ========================================================================== =head1 NAME zmcamtool.pl - ZoneMinder tool to import camera controls and presets =head1 SYNOPSIS zmcamtool.pl [--user= --pass=] [--import [file.sql] [--overwrite]] [--export [name]] [--topreset id [--noregex]] =head1 DESCRIPTION This script provides a way to import new ptz camera controls & camera presets into existing zoneminder systems. This script also provides a way to export ptz camera controls & camera presets from an existing zoneminder system into a sql file, which can then be easily imported to another zoneminder system. =head1 OPTIONS --export - Export all camera controls and presets to STDOUT. Optionally specify a control or preset name. --import [file.sql] - Import new camera controls and presets found in zm_create.sql into the ZoneMinder dB. Optionally specify an alternate sql file to read from. --overwrite - Overwrite any existing controls or presets. with the same name as the new controls or presets. --topreset id - Copy a monitor to a Camera Preset given the monitor id. --noregex - Do not try to find and replace fields such as usernames, passwords, IP addresses, etc with generic placeholders when converting a monitor to a preset. --help - Print usage information. --user= - Alternate dB user with privileges to alter dB. --pass= - Password of alternate dB user with privileges to alter dB. --version - Print version. =cut use strict; use bytes; @EXTRA_PERL_LIB@ use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); use DBI; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $web_uid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; my $use_log = (($> == 0) || ($> == $web_uid)); logInit( toFile=>$use_log?DEBUG:NOLOG ); logSetSignal(); my $export = 0; my $import = 0; my $overwrite = 0; my $help = 0; my $topreset = 0; my $noregex = 0; my $sqlfile = ''; my $dbUser = $Config{ZM_DB_USER}; my $dbPass = $Config{ZM_DB_PASS}; my $version = 0; GetOptions( 'export' =>\$export, 'import' =>\$import, 'overwrite' =>\$overwrite, 'help' =>\$help, 'topreset' =>\$topreset, 'noregex' =>\$noregex, 'user:s' =>\$dbUser, 'pass:s' =>\$dbPass, 'version' =>\$version ) or pod2usage(-exitstatus => -1); $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; if ( $version ) { print( ZoneMinder::Base::ZM_VERSION . "\n"); exit(0); } # Check to make sure commandline params make sense if ( ((!$help) && ($import + $export + $topreset) != 1 )) { print( STDERR qq/Please give only one of the following: "import", "export", or "topreset".\n/ ); pod2usage(-exitstatus => -1); } if ( ($export)&&($overwrite) ) { print( "Warning: Overwrite parameter ignored during an export.\n"); } if ( ($noregex)&&(!$topreset) ) { print( qq/Warning: Noregex parameter only applies when "topreset" parameter is also set. Ignoring.\n/); } if ( ($topreset)&&($ARGV[0] !~ /\d\d*/) ) { print( STDERR qq/Parameter "topreset" requires a valid monitor ID.\n/ ); pod2usage(-exitstatus => -1); } # Call the appropriate subroutine based on the params given on the commandline if ($help) { pod2usage(-exitstatus => -1); } if ($export) { exportsql(); } if ($import) { importsql(); } if ($topreset) { toPreset(); } ############### # SUBROUTINES # ############### # Execute a pre-built sql select query sub selectQuery { my $dbh = shift; my $sql = shift; my $monitorid = shift; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute($monitorid) or die( "Can't execute: ".$sth->errstr() ); my @data = $sth->fetchrow_array(); $sth->finish(); return @data; } # Exectute a pre-built sql query sub runQuery { my $dbh = shift; my $sql = shift; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); return $res; } # Build and execute a sql insert query sub insertQuery { my $dbh = shift; my $tablename = shift; my @data = @_; my $sql = "INSERT INTO $tablename VALUES (NULL," .(join ', ', ('?') x @data).')'; # Add "?" for each array element my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute(@data) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); return $res; } # Build and execute a sql delete query sub deleteQuery { my $dbh = shift; my $sqltable = shift; my $sqlname = shift; my $sql = "DELETE FROM $sqltable WHERE Name = ?"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute($sqlname) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); return $res; } # Build and execute a sql select count query sub checkExists { my $dbh = shift; my $sqltable = shift; my $sqlname = shift; my $result = 0; my $sql = "SELECT count(*) FROM $sqltable WHERE Name = ?"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute($sqlname) or die( "Can't execute: ".$sth->errstr() ); my $rows = $sth->fetchrow_arrayref(); $sth->finish(); if ($rows->[0] > 0) { $result = 1; } return $result; } # Import camera control & presets into the zoneminder dB sub importsql { my @newcontrols; my @overwritecontrols; my @skippedcontrols; my @newpresets; my @overwritepresets; my @skippedpresets; my %controls; my %monitorpresets; if ($ARGV[0]) { $sqlfile = $ARGV[0]; } else { $sqlfile = $Config{ZM_PATH_DATA}.'/db/zm_create.sql'; } open(my $SQLFILE,'<',$sqlfile) or die( "Can't Open file: $!\n" ); # Find and extract ptz control and monitor preset records while (<$SQLFILE>) { # Our regex replaces the primary key with NULL if (s/^(INSERT INTO .*?Controls.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) { $controls{$3} = $_; } elsif (s/^(INSERT INTO .*?MonitorPresets.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) { $monitorpresets{$3} = $_; } } close $SQLFILE; if ( ! (%controls || %monitorpresets) ) { die( "Error: No relevant data found in $sqlfile.\n" ); } # Now that we've got what we were looking for, # compare to what is already in the dB my $dbh = zmDbConnect(); foreach (keys %controls) { if (!checkExists($dbh,'Controls',$_)) { # No existing Control was found. Add new control to dB. runQuery($dbh,$controls{$_}); push @newcontrols, $_; } elsif ($overwrite) { # An existing Control was found and the overwrite flag is set. # Overwrite the control. deleteQuery($dbh,'Controls',$_); runQuery($dbh,$controls{$_}); push @overwritecontrols, $_; } else { # An existing Control was found and the overwrite flag was not set. # Do nothing. push @skippedcontrols, $_; } } foreach (keys %monitorpresets) { if (!checkExists($dbh,'MonitorPresets',$_)) { # No existing MonitorPreset was found. Add new MonitorPreset to dB. runQuery($dbh,$monitorpresets{$_}); push @newpresets, $_; } elsif ($overwrite) { # An existing MonitorPreset was found and the overwrite flag is set. # Overwrite the MonitorPreset. deleteQuery($dbh,'MonitorPresets',$_); runQuery($dbh,$monitorpresets{$_}); push @overwritepresets, $_; } else { # An existing MonitorPreset was found and the overwrite flag was # not set. Do nothing. push @skippedpresets, $_; } } if (@newcontrols) { print 'Number of ptz camera controls added: ' .scalar(@newcontrols)."\n"; } if (@overwritecontrols) { print 'Number of existing ptz camera controls overwritten: ' .scalar(@overwritecontrols)."\n"; } if (@skippedcontrols) { print 'Number of existing ptz camera controls skipped: ' .scalar(@skippedcontrols)."\n"; } if (@newpresets) { print 'Number of monitor presets added: ' .scalar(@newpresets)."\n"; } if (@overwritepresets) { print 'Number of existing monitor presets overwritten: ' .scalar(@overwritepresets)."\n"; } if (@skippedpresets) { print 'Number of existing presets skipped: ' .scalar(@skippedpresets)."\n"; } } # Export camera controls & presets from the zoneminder dB to STDOUT sub exportsql { my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $command = 'mysqldump -t --skip-opt --compact -h'.$host; $command .= ' -P'.$port if defined($port); if ( $dbUser ) { $command .= ' -u'.$dbUser; if ( $dbPass ) { $command .= ' -p'.$dbPass; } } if ($ARGV[0]) { $command .= qq( --where="Name = '$ARGV[0]'"); } $command .= " zm Controls MonitorPresets"; my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { chomp( $output ); print( "Output: $output\n" ); } if ( $status ) { die( "Command '$command' exited with status: $status\n" ); } else { # NULLify the primary keys before printing the output to STDOUT $output =~ s/VALUES \((.*?),'/VALUES \(NULL,'/ig; print $output; } } sub toPreset { my $dbh = zmDbConnect(); my $monitorid = $ARGV[0]; # Grap the following fields from the Monitors table my $sql = 'SELECT Name, Type, Device, Channel, Format, Protocol, Method, Host, Port, Path, SubPath, Width, Height, Palette, MaxFPS, Controllable, ControlId, ControlDevice, ControlAddress, DefaultRate, DefaultScale FROM Monitors WHERE Id = ?'; my @data = selectQuery($dbh,$sql,$monitorid); if (!@data) { die( "Error: Monitor Id $monitorid does not appear to exist in the database.\n" ); } # Attempt to search for and replace system specific values such as # ip addresses, ports, usernames, etc. with generic placeholders if (!$noregex) { foreach (@data) { next if ! $_; s/\b(?:\d{1,3}\.){3}\d{1,3}\b//; # ip address s/:(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$/:/; # tcpip port s/\/\/.*:.*@/\/\/:@/; # user & pwd preceding an ip address s/(&|\?)(user|username)=\w\w*(&|\?)/$1$2=$3/i; # username embedded in url s/(&|\?)(pwd|password)=\w\w*(&|\?)/$1$2=$3/i; # password embedded in url s/\w\w*:\w\w*/:/; # user & pwd in their own field s/\/dev\/video\d\d*/\/dev\/video/; # local video devices } } if (!checkExists($dbh,"MonitorPresets",$data[0])) { # No existing Preset was found. Add new Preset to dB. print "Adding new preset: $data[0]\n"; insertQuery($dbh,'MonitorPresets',@data); } elsif ($overwrite) { # An existing Control was found and the overwrite flag is set. # Overwrite the control. print "Existing preset $data[0] detected.\nOverwriting...\n"; deleteQuery($dbh,'MonitorPresets',$data[0]); insertQuery($dbh,'MonitorPresets',@data); } else { # An existing Control was found and the overwrite flag was not set. # Do nothing. print "Existing preset $data[0] detected and overwrite flag not set.\nSkipping...\n"; } } 1; __END__ ZoneMinder-1.32.2/zmlinkcontent.sh.in0000755000000000000000000002431013365153156016222 0ustar rootroot#!/bin/bash # This tool is used to verify folders critical to ZoneMinder exist and have the right permissions. # It will also create symlinks when necessary. It can use an existing content folder or create a new one. # Set the content dir default to be the one supplied to cmake ZM_PATH_CONTENT="@ZM_CONTENTDIR@" # Set the zoneminder log dir default to be the one supplied to cmake ZM_LOGDIR="@ZM_LOGDIR@" # Set the zoneminder temp dir default to be the one supplied to cmake ZM_TMPDIR="@ZM_TMPDIR@" echo "*** This bash script creates the nessecary symlinks for the zoneminder content" echo "*** It can use an existing content folder or create a new one" echo "*** For usage: use -h" echo "*** The default content directory is: $ZM_PATH_CONTENT" echo "*** The default log directory is: $ZM_LOGDIR" echo "*** The default temp directory is: $ZM_TMPDIR" echo "" usage() { cat <) { $in_head-- if $line =~ /^$/ and $in_head; next while $in_head; unless ($line =~ /^\s+(0x..), \/\* (........)/) { $line =~ s/static unsigned char fontdata/static unsigned int bigfontdata/; print $line; next; } my $code = $1; my $bincode = $2; $bincode = "$1$1$2$2$3$3$4$4$5$5$6$6$7$7$8$8" if $bincode =~ /(.)(.)(.)(.)(.)(.)(.)(.)$/; $bincode =~ s/ /1/g; my $intcode = unpack("N", pack("B32", substr("0" x 32 . $bincode, -32))); my $hexcode = sprintf("%#x", $intcode); $hexcode =~ s/^0$/0x0/; $bincode =~ s/1/ /g; print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); } close F; ZoneMinder-1.32.2/utils/zm-alarm.pl0000755000000000000000000000314613365153155015602 0ustar rootroot#!/usr/bin/env perl # While this script is running, it will print out the state of each alarm on the system. # This script is an example of calling external scripts in reaction to a # monitor changing state. Simply replace the print() commands with system(), # for example, to call external scripts. use strict; use warnings; use ZoneMinder; use Switch; $| = 1; my @monitors; my $dbh = zmDbConnect(); my $sql = "SELECT * FROM Monitors WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )". ( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' ) ; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute '$sql': ".$sth->errstr() ); while ( my $monitor = $sth->fetchrow_hashref() ) { push( @monitors, $monitor ); } while (1) { foreach my $monitor (@monitors) { # Check shared memory ok if ( !zmMemVerify( $monitor ) ) { zmMemInvalidate( $monitor ); next; } my $monitorState = zmGetMonitorState($monitor); printState($monitor->{Id}, $monitor->{Name}, $monitorState); } sleep 1; } sub printState { my ($monitor_id, $monitor_name, $state) = @_; my $time = localtime(); switch ($state) { case 0 { print "$time - $monitor_name:\t Idle!\n" } case 1 { print "$time - $monitor_name:\t Prealarm!\n" } case 2 { print "$time - $monitor_name:\t Alarm!\n" } case 3 { print "$time - $monitor_name:\t Alert!\n" } } } ZoneMinder-1.32.2/utils/travis.pl0000755000000000000000000000051413365153155015366 0ustar rootroot#!/usr/bin/env perl use strict; use warnings; my @tests = ( 'zmpkg.pl start', 'zmfilter.pl -f purgewhenfull', ); sub run_test { my $test = $_[0]; print "Running test: '$test'"; my @args = ('sudo', $test); system(@args) == 0 or die "'$test' failed to run!"; } foreach my $test (@tests) { run_test($test); } ZoneMinder-1.32.2/utils/docker/0000755000000000000000000000000013365153155014765 5ustar rootrootZoneMinder-1.32.2/utils/docker/README.md0000644000000000000000000000020513365153155016241 0ustar rootroot## Docker Support Files Docker support files have been moved to the zmdockerfiles repo: https://github.com/ZoneMinder/zmdockerfiles ZoneMinder-1.32.2/utils/do_debian_package.sh0000755000000000000000000002214113365153155017434 0ustar rootroot#!/bin/bash if [ "$1" == "clean" ]; then read -p "Do you really want to delete existing packages? [y/N]" [[ $REPLY == [yY] ]] && { rm -fr zoneminder*.build zoneminder*.changes zoneminder*.deb; echo "Existing package files deleted"; } || { echo "Packages have NOT been deleted"; } exit; fi DEBUILD=`which debuild`; if [ "$DEBUILD" == "" ]; then echo "You must install the devscripts package. Try sudo apt-get install devscripts"; exit; fi for i in "$@" do case $i in -b=*|--branch=*) BRANCH="${i#*=}" shift # past argument=value ;; -d=*|--distro=*) DISTROS="${i#*=}" shift # past argument=value ;; -i=*|--interactive=*) INTERACTIVE="${i#*=}" shift # past argument=value ;; -r=*|--release=*) RELEASE="${i#*=}" shift ;; -s=*|--snapshot=*) SNAPSHOT="${i#*=}" shift # past argument=value ;; -t=*|--type=*) TYPE="${i#*=}" shift # past argument=value ;; -u=*|--urgency=*) URGENCY="${i#*=}" shift # past argument=value ;; -f=*|--fork=*) GITHUB_FORK="${i#*=}" shift # past argument=value ;; -v=*|--version=*) PACKAGE_VERSION="${i#*=}" shift ;; --default) DEFAULT=YES shift # past argument with no value ;; *) # unknown option read -p "Unknown option $i, continue? (Y|n)" [[ $REPLY == [yY] ]] && { echo "continuing..."; } || exit 1; ;; esac done DATE=`date -R` if [ "$TYPE" == "" ]; then echo "Defaulting to source build" TYPE="source"; else echo "Doing $TYPE build" fi; if [ "$DISTROS" == "" ]; then if [ "$RELEASE" != "" ]; then DISTROS="xenial,bionic,trusty" else DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; fi; echo "Defaulting to $DISTROS for distribution"; else echo "Building for $DISTROS"; fi; # Release is a special mode... it uploads to the release ppa and cannot have a snapshot if [ "$RELEASE" != "" ]; then if [ "$SNAPSHOT" != "" ]; then echo "Releases cannot have a snapshot.... exiting." exit 0; fi if [ "$GITHUB_FORK" != "" ] && [ "$GITHUB_FORK" != "ZoneMinder" ]; then echo "Releases cannot have a fork ($GITHUB_FORK).... exiting." exit 0; else GITHUB_FORK="ZoneMinder"; fi # We use a tag instead of a branch atm. BRANCH=$RELEASE else if [ "$GITHUB_FORK" == "" ]; then echo "Defaulting to ZoneMinder upstream git" GITHUB_FORK="ZoneMinder" fi; if [ "$SNAPSHOT" == "stable" ]; then if [ "$BRANCH" == "" ]; then BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); echo "Latest stable branch is $BRANCH"; fi; else if [ "$BRANCH" == "" ]; then echo "Defaulting to master branch"; BRANCH="master"; fi; if [ "$SNAPSHOT" == "NOW" ]; then SNAPSHOT=`date +%Y%m%d%H%M%S`; fi; fi; fi PPA=""; if [ "$RELEASE" != "" ]; then # We need to use our official tarball for the original source, so grab it and overwrite our generated one. IFS='.' read -r -a VERSION <<< "$RELEASE" PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" else if [ "$BRANCH" == "" ]; then PPA="ppa:iconnor/zoneminder-master"; else PPA="ppa:iconnor/zoneminder-$BRANCH"; fi; fi; # Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then if [ -d "${GITHUB_FORK}_ZoneMinder.git" ]; then echo "Using local clone ${GITHUB_FORK}_ZoneMinder.git to pull from." cd "${GITHUB_FORK}_ZoneMinder.git" echo "git pull..." git pull echo "git checkout $BRANCH" git checkout $BRANCH echo "git pull..." git pull cd ../ echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" else echo "git clone https://github.com/$GITHUB_FORK/ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" git clone "https://github.com/$GITHUB_FORK/ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" fi else echo "release dir already exists. Please remove it." exit 0; fi; cd "${GITHUB_FORK}_zoneminder_release" git checkout $BRANCH cd ../ VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` if [ $VERSION == "" ]; then exit 1; fi; if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then VERSION="$VERSION~$SNAPSHOT"; fi; DIRECTORY="zoneminder_$VERSION"; if [ -d "$DIRECTORY.orig" ]; then echo "$DIRECTORY.orig already exists. Please delete it." exit 0; fi; echo "Doing $TYPE release $DIRECTORY"; mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig"; if [ $? -ne 0 ]; then echo "Error status code is: $?" echo "Setting up build dir failed."; exit $?; fi; cd "$DIRECTORY.orig"; # Init submodules git submodule init git submodule update --init --recursive # Cleanup rm -rf .git rm .gitignore cd ../ if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig fi; IFS=',' ;for DISTRO in `echo "$DISTROS"`; do echo "Generating package for $DISTRO"; cd $DIRECTORY.orig if [ -e "debian" ]; then rm -rf debian fi; # Generate Changlog if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then cp -Rpd distros/ubuntu1204 debian else if [ "$DISTRO" == "wheezy" ]; then cp -Rpd distros/debian debian else cp -Rpd distros/ubuntu1604 debian fi; fi; if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then AUTHOR="$DEBFULLNAME <$DEBEMAIL>" else if [ -z `hostname -d` ] ; then AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" else AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" fi fi if [ "$URGENCY" = "" ]; then URGENCY="medium" fi; if [ "$SNAPSHOT" == "stable" ]; then cat < debian/changelog zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY * Release $VERSION -- $AUTHOR $DATE EOF cat < debian/NEWS zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY * Release $VERSION -- $AUTHOR $DATE EOF else cat < debian/changelog zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY * -- $AUTHOR $DATE EOF cat < debian/changelog zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY * -- $AUTHOR $DATE EOF fi; if [ $TYPE == "binary" ]; then # Auto-install all ZoneMinder's depedencies using the Debian control file sudo apt-get install devscripts equivs sudo mk-build-deps -ir ./debian/control echo "Status: $?" DEBUILD=debuild else if [ $TYPE == "local" ]; then # Auto-install all ZoneMinder's depedencies using the Debian control file sudo apt-get install devscripts equivs sudo mk-build-deps -ir ./debian/control echo "Status: $?" DEBUILD="debuild -i -us -uc -b" else # Source build, don't need build depends. DEBUILD="debuild -S -sa" fi; fi; if [ "$DEBSIGN_KEYID" != "" ]; then DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" fi eval $DEBUILD if [ $? -ne 0 ]; then echo "Error status code is: $?" echo "Build failed."; exit $?; fi; cd ../ if [ $TYPE == "binary" ]; then if [ "$INTERACTIVE" != "no" ]; then read -p "Not doing dput since it's a binary release. Do you want to install it? (y/N)" if [[ $REPLY == [yY] ]]; then sudo dpkg -i $DIRECTORY*.deb fi; read -p "Do you want to upload this binary to zmrepo? (y/N)" if [[ $REPLY == [yY] ]]; then if [ "$RELEASE" != "" ]; then scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/" else if [ "$BRANCH" == "" ]; then scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/" else scp "$DIRECTORY-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/${BRANCH}/mini-dinstall/incoming/" fi; fi; fi; fi; else SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; dput="Y"; if [ "$INTERACTIVE" != "no" ]; then read -p "Ready to dput $SC to $PPA ? Y/N..."; if [[ "$REPLY" == [yY] ]]; then dput $PPA $SC fi; fi; fi; done; # foreach distro if [ "$INTERACTIVE" != "no" ]; then read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]" [[ $REPLY == [yY] ]] && { mv "$DIRECTORY.orig" zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; } echo "Done!" else rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; fi ZoneMinder-1.32.2/utils/generate_apache_config.pl0000755000000000000000000000740013365153155020477 0ustar rootroot#!/usr/bin/perl -w use strict; use Getopt::Long (); my $opts = {}; Getopt::Long::GetOptions($opts, 'help', 'output=s', 'pid_file=s', 'min_port=s','max_port=s', 'debug=s', 'server_name=s','error_log=s','protocol=s', ); if ($opts->{help}) { usage(); exit 0; } # end if my %defaults = ( error_log => '/var/log/apache2/error.log', output => '/etc/apache2/sites-available/zoneminder.conf', protocol => 'http', ); foreach my $key ( keys %defaults ) { if ( ! $$opts{$key} ) { $$opts{$key} = $defaults{$key}; } } my $Listen = ''; my $VirtualHostPorts; if ( $$opts{protocol} eq 'https' ) { if ( ! $$opts{server_name} ) { die "https requires a server_name"; } $VirtualHostPorts = ' *:443'; } else { $VirtualHostPorts = ' *:80'; } foreach my $port ( $$opts{min_port} .. $$opts{max_port} ) { $Listen .= "Listen $port $$opts{protocol}\n"; $VirtualHostPorts .= " *:$port"; } my $template =qq` $Listen DocumentRoot /usr/share/zoneminder/www `. ( $$opts{server_name} ? ' ServerName ' . $$opts{server_name} : '' ). qq` ErrorLog $$opts{error_log} Alias /zm/cache "/var/cache/zoneminder/cache" Alias /cache "/var/cache/zoneminder/cache" Options -Indexes +FollowSymLinks AllowOverride None # Apache 2.4 Require all granted # Apache 2.2 Order deny,allow Allow from all ScriptAlias /zm/cgi-bin/ /usr/lib/zoneminder/cgi-bin/ ScriptAlias /cgi-bin/ /usr/lib/zoneminder/cgi-bin/ AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Require all granted Satisfy Any Order allow,deny Allow from all Alias /zm /usr/share/zoneminder/www php_flag register_globals off Options +Indexes +FollowSymLinks AllowOverride All DirectoryIndex index.php Require all granted Satisfy Any Order allow,deny Allow from all # For better visibility, the following directives have been migrated from the # default .htaccess files included with the CakePHP project. # Parameters not set here are inherited from the parent directive above. RewriteEngine on RewriteRule ^$ app/webroot/ [L] RewriteRule (.*) app/webroot/$1 [L] RewriteBase /zm/api RewriteEngine on RewriteRule ^$ webroot/ [L] RewriteRule (.*) webroot/$1 [L] RewriteBase /zm/api RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] RewriteBase /zm/api `; if ( $$opts{protocol} eq 'https' ) { $template .= qq` SSLCertificateFile /etc/letsencrypt/live/$$opts{server_name}/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/$$opts{server_name}/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf `; } $template .= qq` `; if ( open( F, "> $$opts{output}" ) ) { binmode F; print F $template; close F; } else { die "Error opening $$opts{output}, Reason: $!"; } # end if sub usage { print " Usage: generate-apache-config.pl --help output this help. --output=file the file to output the config to, --min_port= The starting port. port 80 or 443 will be added as appropriate depending on protocol. --max_port= The ending port. --debug= more verbose output --server_name=[servername] --error_log --protocol=[http|https] Whether to turn on https for this host. Will assume a letsencrypt setup for keys. "; exit 1; } 1; __END__ ZoneMinder-1.32.2/utils/packpack/0000755000000000000000000000000013365153155015273 5ustar rootrootZoneMinder-1.32.2/utils/packpack/rsync_xfer.sh0000755000000000000000000000257513365153155020025 0ustar rootroot#!/bin/bash # Check to see if this script has access to all the commands it needs for CMD in sshfs rsync find fusermount mkdir; do type $CMD 2>&1 > /dev/null if [ $? -ne 0 ]; then echo echo "ERROR: The script cannot find the required command \"${CMD}\"." echo exit 1 fi done # We only want to deploy packages during cron events # See https://docs.travis-ci.com/user/cron-jobs/ if [ "${TRAVIS_EVENT_TYPE}" == "cron" ]; then if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then targetfolder="debian/master/mini-dinstall/incoming" else targetfolder="travis" fi echo echo "Target subfolder set to $targetfolder" echo mkdir -p ./zmrepo ssh_mntchk="$(sshfs zmrepo@zmrepo.zoneminder.com:./ ./zmrepo -o workaround=rename,reconnect 2>&1)" if [ -z "$ssh_mntchk" ]; then echo echo "Remote filesystem mounted successfully." echo "Begin transfering files..." echo # Don't keep packages older than 5 days find ./zmrepo/$targetfolder/ -maxdepth 1 -type f -mtime +5 -delete rsync -vzlh --ignore-errors build/* zmrepo/$targetfolder/ fusermount -zu zmrepo else echo echo "ERROR: Attempt to mount zmrepo.zoneminder.com failed!" echo "sshfs gave the following error message:" echo \"$ssh_mntchk\" echo exit 99 fi fi ZoneMinder-1.32.2/utils/packpack/installzm.sh0000755000000000000000000000102213365153155017642 0ustar rootroot#!/bin/bash # No longer needed. This was incorporated into startpackpack.sh # Required, so that Travis marks the build as failed if any of the steps below fail set -ev # Install and test the zoneminder package (only) for Ubuntu Trusty if [ ${OS} == "ubuntu" ] && [ ${DIST} == "trusty" ]; then sudo gdebi --non-interactive build/zoneminder_*amd64.deb sudo chmod 644 /etc/zm/zm.conf mysql -uzmuser -pzmpass zm < db/test.monitor.sql sudo /usr/bin/zmpkg.pl start sudo /usr/bin/zmfilter.pl -f purgewhenfull fi ZoneMinder-1.32.2/utils/packpack/nolintian.patch0000644000000000000000000000172713365153155020316 0ustar rootrootFrom 634281a4204467b9a3c8a1a5febcc8dd9828e0f6 Mon Sep 17 00:00:00 2001 From: Andy Bauer Date: Thu, 22 Feb 2018 08:53:50 -0600 Subject: [PATCH] don't run lintian checks to speed up build --- pack/deb.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pack/deb.mk b/pack/deb.mk index de4a0b7..bddf9df 100644 --- a/packpack/pack/deb.mk +++ b/packpack/pack/deb.mk @@ -130,7 +130,7 @@ $(BUILDDIR)/$(DPKG_CHANGES): $(BUILDDIR)/$(PRODUCT)-$(VERSION)/debian \ @echo "Building Debian packages" @echo "-------------------------------------------------------------------" cd $(BUILDDIR)/$(PRODUCT)-$(VERSION) && \ - debuild --preserve-envvar CCACHE_DIR --prepend-path=/usr/lib/ccache \ + debuild --no-lintian --preserve-envvar CCACHE_DIR --prepend-path=/usr/lib/ccache \ -Z$(TARBALL_COMPRESSOR) -uc -us $(SMPFLAGS) rm -rf $(BUILDDIR)/$(PRODUCT)-$(VERSION)/ @echo "------------------------------------------------------------------" ZoneMinder-1.32.2/utils/packpack/startpackpack.sh0000755000000000000000000002765513365153155020504 0ustar rootroot#!/bin/bash # packpack setup file for the ZoneMinder project # Written by Andrew Bauer ############### # SUBROUTINES # ############### # General sanity checks checksanity () { # Check to see if this script has access to all the commands it needs for CMD in set echo curl git ln mkdir rmdir cat patch; do type $CMD 2>&1 > /dev/null if [ $? -ne 0 ]; then echo echo "ERROR: The script cannot find the required command \"${CMD}\"." echo exit 1 fi done # Verify OS & DIST environment variables have been set before calling this script if [ -z "${OS}" ] || [ -z "${DIST}" ]; then echo "ERROR: both OS and DIST environment variables must be set" exit 1 fi if [ -z "${ARCH}" ]; then ARCH="x86_64" fi if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" ]]; then echo echo "ERROR: Unsupported architecture specified \"${ARCH}\"." echo exit 1 fi } # Create key variables used to assemble the package name createvars () { # We need today's date in year/month/day format thedate=$(date +%Y%m%d) # We need the (short) commit hash of the latest commit (rpm packaging only) shorthash=$(git describe --long --always | awk -F - '{print $3}') # Grab the ZoneMinder version from the contents of the version file versionfile=$(cat version) # git the latest (short) commit hash of the version file versionhash=$(git log -n1 --pretty=format:%h version) # Number of commits since the version file was last changed numcommits=$(git rev-list ${versionhash}..HEAD --count) } # Check key variables before calling packpack checkvars () { for var in $thedate $shorthash $versionfile $versionhash $numcommits; do if [ -z ${var} ]; then echo echo "FATAL: This script was unable to determine one or more key variables. Cannot continue." echo echo "VARIABLE DUMP" echo "-------------" echo echo "thedate: ${thedate}" echo "shorthash: ${shorthash}" echo "versionfile: ${versionfile}" echo "versionhash: ${versionhash}" echo "numcommits: ${numcommits}" echo exit 98 fi done } # Steps common to all builds commonprep () { mkdir -p build if [ -e "packpack/Makefile" ]; then echo "Checking packpack github repo for changes..." git -C packpack pull origin master else echo "Cloning packpack github repo..." git clone https://github.com/packpack/packpack.git packpack fi # Rpm builds are broken in latest packpack master. Temporarily roll back. #git -C packpack checkout 7cf23ee # Patch packpack patch --dry-run --silent -f -p1 < utils/packpack/packpack-rpm.patch if [ $? -eq 0 ]; then patch -p1 < utils/packpack/packpack-rpm.patch fi # Skip deb lintian checks to speed up the build patch --dry-run --silent -f -p1 < utils/packpack/nolintian.patch if [ $? -eq 0 ]; then patch -p1 < utils/packpack/nolintian.patch fi # fix 32bit rpm builds # FIXME: breaks arm rpm builds #patch --dry-run --silent -f -p1 < utils/packpack/setarch.patch #if [ $? -eq 0 ]; then # patch -p1 < utils/packpack/setarch.patch #fi # The rpm specfile requires we download each submodule as a tarball then manually move it into place # Might as well do this for Debian as well, rather than git submodule init CRUDVER="3.1.0-zm" if [ -e "build/crud-${CRUDVER}.tar.gz" ]; then echo "Found existing Crud ${CRUDVER} tarball..." else echo "Retrieving Crud ${CRUDVER} submodule..." curl -L https://github.com/ZoneMinder/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz if [ $? -ne 0 ]; then echo "ERROR: Crud tarball retreival failed..." exit 1 fi fi CEBVER="1.0-zm" if [ -e "build/cakephp-enum-behavior-${CEBVER}.tar.gz" ]; then echo "Found existing CakePHP-Enum-Behavior ${CEBVER} tarball..." else echo "Retrieving CakePHP-Enum-Behavior ${CEBVER} submodule..." curl -L https://github.com/ZoneMinder/CakePHP-Enum-Behavior/archive/${CEBVER}.tar.gz > build/cakephp-enum-behavior-${CEBVER}.tar.gz if [ $? -ne 0 ]; then echo "ERROR: CakePHP-Enum-Behavior tarball retreival failed..." exit 1 fi fi } # Uncompress the submodule tarballs and move them into place movecrud () { if [ -e "web/api/app/Plugin/Crud/LICENSE.txt" ]; then echo "Crud plugin already installed..." else echo "Unpacking Crud plugin..." tar -xzf build/crud-${CRUDVER}.tar.gz rmdir web/api/app/Plugin/Crud mv -f crud-${CRUDVER} web/api/app/Plugin/Crud fi if [ -e "web/api/app/Plugin/CakePHP-Enum-Behavior/readme.md" ]; then echo "CakePHP-Enum-Behavior plugin already installed..." else echo "Unpacking CakePHP-Enum-Behavior plugin..." tar -xzf build/cakephp-enum-behavior-${CEBVER}.tar.gz rmdir web/api/app/Plugin/CakePHP-Enum-Behavior mv -f CakePHP-Enum-Behavior-${CEBVER} web/api/app/Plugin/CakePHP-Enum-Behavior fi } # previsouly part of installzm.sh # install the trusty deb and test zoneminder installtrusty () { # Check we've got gdebi installed type gdebi 2>&1 > /dev/null if [ $? -ne 0 ]; then echo echo "ERROR: The script cannot find the required command \"gdebi\"." echo exit 1 fi # Install and test the zoneminder package (only) for Ubuntu Trusty pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb" if [ -e $pkgname ]; then sudo gdebi --quiet --non-interactive $pkgname mysql -uzmuser -pzmpass zm < db/test.monitor.sql sudo /usr/bin/zmpkg.pl start sudo /usr/bin/zmfilter.pl -f purgewhenfull else echo echo "ERROR: The script cannot find the package $pkgname" echo "Check the Travis log for a build failure." echo exit 99 fi } # This sets the naming convention for the rpm packages setrpmpkgname () { createvars # Set VERSION to the contents of the version file e.g. 1.31.0 # Set RELEASE to 1.{number of commits}.{today's date}git{short hash of HEAD} e.g. 1.82.20170605gitg7ae0b4a export VERSION="$versionfile" export RELEASE="1.${numcommits}.${thedate}git${shorthash}" checkvars echo echo "Packpack VERSION has been set to: ${VERSION}" echo "Packpack RELEASE has been set to: ${RELEASE}" echo } # This sets the naming convention for the deb packages setdebpkgname () { createvars # Set VERSION to {zm version}~{today's date}.{number of commits} e.g. 1.31.0~20170605.82 # Set RELEASE to the packpack DIST variable e.g. Trusty export VERSION="${versionfile}~${thedate}.${numcommits}" export RELEASE="${DIST}" checkvars echo echo "Packpack VERSION has been set to: ${VERSION}" echo "Packpack RELEASE has been set to: ${RELEASE}" echo } # This adds an entry to the rpm specfile changelog setrpmchangelog () { export CHANGELOG_NAME="Andrew Bauer" export CHANGELOG_EMAIL="zonexpertconsulting@outlook.com" export CHANGELOG_TEXT="Automated, development snapshot of git ${shorthash}" } # This adds an entry to the debian changelog setdebchangelog () { DATE=`date -R` cat < debian/changelog zoneminder ($VERSION-${DIST}-1) unstable; urgency=low * -- Isaac Connor $DATE EOF } # start packpack, filter the output if we are running in travis execpackpack () { if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then parms="-f utils/packpack/redhat_package.mk redhat_package" else parms="" fi if [ "${TRAVIS}" == "true" ]; then # Travis will fail the build if the output gets too long # To mitigate that, use grep to filter out some of the noise if [ "${ARCH}" != "armhf" ]; then packpack/packpack $parms | grep -Ev '^(-- Installing:|-- Up-to-date:|Skip blib|Manifying|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])' else # Travis never ceases to amaze. For the case of arm emulation, Travis fails the build due to too little output over a 10 minute period. Facepalm. packpack/packpack $parms | grep -Ev '^(-- Installing:|Skip blib|Manifying|Installing /build|cp lib|writing output...|copying images...|reading sources...|[Working])' fi else packpack/packpack $parms fi } # Check for connectivity with the deploy target host checkdeploytarget () { echo echo "Checking Internet connectivity with the deploy host ${DEPLOYTARGET}" echo ping -c 1 ${DEPLOYTARGET} if [ $? -ne 0 ]; then echo echo "*** WARNING: THERE WAS A PROBLEM CONNECTING TO THE DEPLOY HOST ***" echo echo "Printing additional diagnostic information..." echo echo "*** NSLOOKUP ***" echo nslookup ${DEPLOYTARGET} echo echo "*** TRACEROUTE ***" echo traceroute -w 2 -m 15 ${DEPLOYTARGET} fi } ################ # MAIN PROGRAM # ################ # Set the hostname we will deploy packages to DEPLOYTARGET="zmrepo.zoneminder.com" # If we are running inside Travis then verify we can connect to the target host machine if [ "${TRAVIS}" == "true" ]; then checkdeploytarget fi checksanity # We don't want to build packages for all supported distros after every commit # Only build all packages when executed via cron # See https://docs.travis-ci.com/user/cron-jobs/ if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${TRAVIS}" != "true" ]; then commonprep # Steps common to Redhat distros if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then echo "Begin Redhat build..." setrpmpkgname ln -sfT distros/redhat rpm # The rpm specfile requires the Crud submodule folder to be empty rm -rf web/api/app/Plugin/Crud mkdir web/api/app/Plugin/Crud reporpm="rpmfusion-free-release" dlurl="https://download1.rpmfusion.org/free/${OS}/${reporpm}-${DIST}.noarch.rpm" # Give our downloaded repo rpm a common name so redhat_package.mk can find it if [ -n "$dlurl" ] && [ $? -eq 0 ]; then echo "Retrieving ${reporpm} repo rpm..." curl $dlurl > build/external-repo.noarch.rpm else echo "ERROR: Failed to retrieve ${reporpm} repo rpm..." echo "Download url was: $dlurl" exit 1 fi setrpmchangelog echo "Starting packpack..." execpackpack # Steps common to Debian based distros elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then echo "Begin ${OS} ${DIST} build..." setdebpkgname movecrud if [ "${DIST}" == "trusty" ] || [ "${DIST}" == "precise" ]; then ln -sfT distros/ubuntu1204 debian elif [ "${DIST}" == "wheezy" ]; then ln -sfT distros/debian debian else ln -sfT distros/ubuntu1604 debian fi setdebchangelog echo "Starting packpack..." execpackpack if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "trusty" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then installtrusty fi fi # We were not triggered via cron so just build and test trusty elif [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "trusty" ] && [ "${ARCH}" == "x86_64" ]; then echo "Begin Ubuntu Trusty build..." commonprep setdebpkgname movecrud ln -sfT distros/ubuntu1204 debian setdebchangelog echo "Starting packpack..." execpackpack # If we are running inside Travis then attempt to install the deb we just built if [ "${TRAVIS}" == "true" ]; then installtrusty fi fi exit 0 ZoneMinder-1.32.2/utils/packpack/deploy_rsa.enc0000644000000000000000000000626013365153155020127 0ustar rootroot'ตg]ฉ`๓๕!# 9]ํtˆฺ๛’Lฤๅz(”ณ&ŽฯสSk%&inx ‡ฝIฬซˆ๋E็จ< ฑ…\ป™ิฒc%AQ-›ำบ,‹Dฝ"ืาVaOโ๋ฆwจื๛$ ห-็VcแณฃWD˜Rถ„ถ‹มZ’2H‚ฒ5v†ชอท%2lห.,๕Yฅ`“ฎฏฌl*ž&e|mำ&'ฒa่2ฝเฉTขฦฝ“รj*์-๛o>ub9ใร๒ lฌ Fั จJPiY๖.mฝM”ชฬ'๏ฮ@i26๏–™S็ะผDธ~OๅฬYึ˜o๐[”ๅa฿ส–G8ฦ)‚‹]์ฤ€‚Oภ‚ฃ–‘‰๔‰^ปd{๖Žึ†‹ฬ”฿K)Œ/ฅ๏HหฬฯสJe}yฌกฦ,'NโF”F {U๖>/๑]’2w}Pฐึ‡P1œz\ gmp8RภZa(จน …vทtโ5พ(ipนz๗Yฎหไ2;ะทผŠ4„ชฏbคฦศผธฉCัฦiTX ฎRQฬขใeˆสs์ณฑW^หwิQถyส"ฃŒ™FษŽP฿็’3[qศ‚๕บ ๑^ฤแeoVgPท—ฏ“ฐoDx๗dฝ,‡๕Ÿe ฟ+C0-ว ืKๅฤชษ฿QิEโ‹dปD<ธ‚๚@mุ‚‹Cอ้<สึ…ิ2ทอ} F$ jyTๅฎ,$๎๙k›y-”)l๋ฒŠ,|ๆ8i%ˆ๏‹ฯ›[@ •ุบy๛„œฺGK๊:ไบ๊b€-ย์8—ฑ!่‚๙”š๐ๅMKฃŸ ้๏่,O1ถcjœ=9vŽu˜โf—ฤแV๓Lq—`X๔€ูwช+‡ฃฤbu<ˆŸK๏__Wlา)ี๗m—`]ธbeXี&}<ฤ๑‚ ฒจึด$\แั]89j”คQ๐เเLหภ3tไ_ม๚‡< }ŒเQถh‘—ๆˆ€L —*]๓ฑาƒDฆZธ่ฬไ~ส`ฐy'D๛!หฑx"K็RŽ.…{ด= น๛ไนmู๖็…ฦฉUu#๊ิ่ํ–oท\ƒ๏Lึ_~ฏขวไg๓หfmi้-V็Yฐ๛ ฑิdOEำ‘’ฯfั”Xถ“ำe” Xcต.ห{ๆอ@๕"w@)Q:P_n้P2=๒ร;ะebญqก$zmN’š๗…ง๚t(Fฉํ]๑O›าPZ์ฉLoฏ‹†Ts ฅผƒ=~เXฟ—๑)ผx‚ฐ>…‘๘Œ๑zรฏTpWคขไkD0t ๎”าฎeVหvพ|‹V‘ีk๓"ตXถม๖r’ตๅ ใ=ycฑ๑2-5|ซท9ฌ[CMbHใ๎ตว+}ˆYgLผ‹๔๒คPา 'ใ†˜๗Bฬ๛ ”c‰‚รAm/Ž$?t๑ห\ฺ ช.‡c|กฬ์‹ %๖"y`ไฺn ไฦX.๘3Aฏ]จ๎ คุSnKห~ฐภถzหaฑSฺุ>ฏ–อ/:ดfูฒ˜บฎฉŽ“ไl(๊์ใ{Z๔๊+ p‡ฐำˆ,VŒูXโPk6‘žถx{ !Cมrำๆ™฿#Y…ส๊uŒI1%O.H`k=R๊‹อˆวXญdDเa—Y7Hขฏุฺ ’—ู{ก0ตัพ๖N1ผาe(๊ตตฮป>j๒ืP๛œชKv„SA๕'0a฿[w@Kฃ<=cุ•(_็ ฟ}๓aง_EV‹Pใฐ™pฤธษzยWรคb$> ชฑู|€uu|ํา5น(t่Z.†…9[?Ÿฬจ‡ฤpE7Oี๙)N8H๓๋VdQCGนAsiฺ"ิ๔)ีq)Œ1๓๕ีศq‚9‰rฬๆq+บ>'ๅ‡ฒ&`๒๐a qน๖ทI#๗ใ-๑๕IŸง{eAAv%žฏ|7r์’VพyIx;œcฏiฒ’›\X–'LูหRจFฌdิ‰ะYKgฏำ๔ณcoGม–J฿Wหืฐสฯ Y=:>ŽCค๐จใ yลD๘ใ&ฏ๖"]ซ…w๊ป8šฦg Ÿ›zฒฺ>๙d_่ฟ ฎื Cุ รŽ†ฅY˜QึP^ย๛จŽŸ~ต—Œ้3_7‹๙กz‚อ<ช”c่`!า8{Yไฝ*ฏิkฑ,ูธ"ถvฒึ๗MNญu5x‰ E<>$ไC {ผm4‡๙ฃฅ;้ึD๕ษ_ฒช‚~?ุๅz]PK’ฃีฦ8ส Zมต5ZT?—s,๒๘…๓๚]ฟุ9ซ ฯ?ป“z2พโ*อฉ_๓ aใ~ฌฝ%C\qจ ขG€%^@*œฒุ จบc q˜—O1—ฦี๛=ๆw๓o้.ีํ‡m†7ดํเ๔Tcฑฟ$…ัฒj<˜“ก๓&๛ ฿Jf“jบ^„๓Jท.„ฦ€ž“JIฺ‡ho6ฤrz5ฬงแฅIว˜Xปhฃํ™&ึฒฮฏ?ฺ๚ครS๓G๊q+โๅQqป/K.HV]z็…ฐศฒŒ๋‰ผ_น๕ฑ=บ ูk>๑`๔6{ฐมปšv-Vaลึ›“ฏ๒< น๛๛๘ขjคณหด‹`ํล๓ห)#$ทุ)}๑ส;/ุถpi`Wxดี8•0›ผ$vW~-ฮดzp๗#)“๊O.&\๕yอOซกUUWะ{ฃœนจ—'.)VœB@GO๊~D&m^๔=›ยคVZoneMinder-1.32.2/utils/packpack/heartbeat.sh0000755000000000000000000000035113365153155017570 0ustar rootroot#!/bin/bash # A script to provide background noise so Travis doesn't kill us due to inactivity # written by Andrew Bauer while true; do echo "$(date) - Please don't kill us Mr. Travis, we are still running!" sleep 30s done ZoneMinder-1.32.2/utils/packpack/deploy_rsa0000644000000000000000000000136213365153155017361 0ustar rootrootssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6tO+Kuj85uS4Z7ZmyAjmeAJbKbuv+pddPncgY/0/cSKfLbDo5f+TFM9K5AUlegcALbccUA9jt5p615KTgN0ZyGiCDcNs6/1DcDh4Nb/KgHsApVLZHZYpd7bnP5yadIqXroXw9VI2PWhUTasb97t4xvEepcsnepdfeGFegX7jyeRqucdEsZZa8kiSgU9hIdJyTeQXQQY5odYqABvR6ea7ff8iD2pdzjXIFHiA9527fXoPuUJo5rwIWOwtwstFG5xCT6ZPBgi2nECSZoRdG7zqkm2gqhAaNiR8PR5Qr0CbeYa4LWYl0v33CwLxFuyiP9AOqdFB+vF9c67do4E0yJ+heUNuOUbp3ePT1jJDOhQapAjkmyEb4A+RMNc9SEXmJh10nUiJ2zGGb9a3FyreopHfjdzKiZdk+uSV18kdG7KSiRvHep+sEK7xMepAQ3OiAprwOH2D2EoXC08TLehaeXmsOAuMGz0JvJKBXMGAow2SEa38v42PJwKsjn0qo72IjN1h/9ICgM/o+4oNHLHsu5GK1dornAt9OE6g1be1jXdij07QaSV831NiweOHqRTPpl2eglWfoyMjPuzebiyoAqb4tNBnMOk5BTaxx7ZZGXmdHyH9kKFAVj+WBkRHNlDhaMB2/QzhHUs0PRVRUexBKgog+1xnPE5gTie6NNnDB04NcDQ== zmrepo@zmrepo.zoneminder.com ZoneMinder-1.32.2/utils/packpack/packpack-rpm.patch0000644000000000000000000000111413365153155020662 0ustar rootroot--- a/packpack/pack/rpm.mk 2017-05-10 09:53:38.797616947 -0500 +++ b/packpack/pack/rpm.mk 2017-05-10 09:59:26.744409073 -0500 @@ -26,7 +26,6 @@ -e 's/Source0:\([ ]*\).*/Source0: $(TARBALL)/' \ -e 's/%setup.*/%setup -q -n $(PRODUCT)-$(VERSION)/' \ -re 's/(%autosetup.*)( -n \S*)(.*)/\1\3/' \ - -e '0,/%autosetup.*/ s/%autosetup.*/%autosetup -n $(PRODUCT)-$(VERSION)/' \ -e '/%changelog/a\* $(THEDATE) $(CHANGELOG_NAME) <$(CHANGELOG_EMAIL)> - $(VERSION)-$(RELEASE)\n\- $(CHANGELOG_TEXT)\n' \ -i $@.tmp grep -F "Version: $(VERSION)" $@.tmp && \ ZoneMinder-1.32.2/utils/packpack/setarch.patch0000644000000000000000000000376513365153155017760 0ustar rootrootFrom 62a98b36fd62d328956503bc9427ae128bb811af Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Mon, 26 Feb 2018 10:05:02 -0600 Subject: [PATCH] fix 32bit rpm builds --- pack/rpm.mk | 2 +- packpack | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packpack/pack/rpm.mk b/packpack/pack/rpm.mk index c74e942..9a6b016 100644 --- a/packpack/pack/rpm.mk +++ b/packpack/pack/rpm.mk @@ -124,7 +124,7 @@ package: $(BUILDDIR)/$(RPMSRC) @echo "-------------------------------------------------------------------" @echo "Building RPM packages" @echo "-------------------------------------------------------------------" - rpmbuild \ + setarch $(ARCH) rpmbuild \ --define '_topdir $(BUILDDIR)' \ --define '_sourcedir $(BUILDDIR)' \ --define '_specdir $(BUILDDIR)' \ diff --git a/packpack/packpack b/packpack/packpack index 6f4c80f..c329399 100755 --- a/packpack/packpack +++ b/packpack/packpack @@ -125,7 +125,7 @@ chmod a+x ${BUILDDIR}/userwrapper.sh # # Save defined configuration variables to ./env file # -env | grep -E "^PRODUCT=|^VERSION=|^RELEASE=|^ABBREV=|^TARBALL_|^CHANGELOG_|^CCACHE_|^PACKAGECLOUD_|^SMPFLAGS=|^OS=|^DIST=" \ +env | grep -E "^PRODUCT=|^VERSION=|^RELEASE=|^ABBREV=|^TARBALL_|^CHANGELOG_|^CCACHE_|^PACKAGECLOUD_|^SMPFLAGS=|^OS=|^DIST=|^ARCH=" \ > ${BUILDDIR}/env # diff --git a/packpack/packpack b/packpack/packpack index c329399..6ffaa9c 100755 --- a/packpack/packpack +++ b/packpack/packpack @@ -19,11 +19,11 @@ DOCKER_REPO=${DOCKER_REPO:-packpack/packpack} if [ -z "${ARCH}" ]; then # Use uname -m instead of HOSTTYPE case "$(uname -m)" in - i*86) ARCH="i386" ;; - arm*) ARCH="armhf" ;; - x86_64) ARCH="x86_64"; ;; - aarch64) ARCH="aarch64" ;; - *) ARCH="${HOSTTYPE}" ;; + i*86) export ARCH="i386" ;; + arm*) export ARCH="armhf" ;; + x86_64) export ARCH="x86_64"; ;; + aarch64) export ARCH="aarch64" ;; + *) export ARCH="${HOSTTYPE}" ;; esac fi ZoneMinder-1.32.2/utils/packpack/redhat_package.mk0000644000000000000000000000026313365153155020547 0ustar rootroot.PHONY: redhat_package .NOTPARALLEL: redhat_package redhat_package: redhat_bootstrap package redhat_bootstrap: sudo yum install -y --nogpgcheck build/external-repo.noarch.rpm ZoneMinder-1.32.2/utils/zmeditconfigdata.sh0000755000000000000000000000423113365153155017371 0ustar rootroot#!/bin/bash # This script allows the package maintainer to change the default value # of any variable specified in ConfigData.pm without writing a patch. # Run this script from your build folder, before running configure or cmake. usage() { cat < /dev/null if [ $? -ne 0 ]; then echo echo "ERROR: The script cannot find the required command \"${CMD}\"." echo exit 1 fi done escape() { escaped="" local temp="$(printf %q "$1")" escaped="$(echo $temp | sed 's/\//\\\//g')" } # Assign variables once they are properly escaped escape "$1" variable="$escaped" escape "$2" default="$escaped" # Set the path to ConfigData if [ -n "$3" ]; then configdata="$3/ConfigData.pm.in" else configdata="./scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in" fi # Check to make sure we can find ConfigData if [ ! -e "$configdata" ]; then echo "CONFIGDATA FILE NOT FOUND: $configdata" exit 1 fi # Now that we've found ConfidData, verify the supplied variable # is defined inside the ConfigData file. if [ -z "$(grep $variable $configdata)" ]; then echo "ZONEMINDER VARIABLE NOT FOUND: $variable" exit 1 fi # Update the supplied variable with the new default value. # Don't stare too closely. You will burn your eyes out. sed -i '/.*'${variable}'.*/{ $!{ N s/\(.*'${variable}'.*\n.*\)'\''\(.*\)'\''/\1'\'''"${default}"''\''/ t yes P D :yes } }' $configdata if [ "$?" != "0" ]; then echo "SED RETURNED FAILURE" exit 1 fi ZoneMinder-1.32.2/utils/travis/0000755000000000000000000000000013365153155015026 5ustar rootrootZoneMinder-1.32.2/utils/travis/build-zm.sh0000644000000000000000000000142713365153155017111 0ustar rootroot#!/bin/bash set -e set -o pipefail with_timestamps() { while read -r line; do echo -e "$(date +%T)\t$line"; done } cd $TRAVIS_BUILD_DIR build_zm() { if [ "$ZM_BUILDMETHOD" = "autotools" ]; then ./configure --prefix=/usr --with-libarch=lib/$DEB_HOST_GNU_TYPE --host=$DEB_HOST_GNU_TYPE --build=$DEB_BUILD_GNU_TYPE --with-mysql=/usr --with-ffmpeg=/usr --with-webdir=/usr/share/zoneminder/www --with-cgidir=/usr/libexec/zoneminder/cgi-bin --with-webuser=www-data --with-webgroup=www-data --enable-crashtrace=yes --disable-debug --enable-mmap=yes ZM_SSL_LIB=openssl fi if [ "$ZM_BUILDMETHOD" = "cmake" ]; then cmake -DCMAKE_INSTALL_PREFIX="/usr" fi make sudo make install if [ "$ZM_BUILDMETHOD" = "cmake" ]; then sudo ./zmlinkcontent.sh fi } build_zm | with_timestamps ZoneMinder-1.32.2/utils/travis/install-ffmpeg.sh0000644000000000000000000000050513365153155020272 0ustar rootroot#!/bin/bash set -e git clone --depth=10 --branch=master git://source.ffmpeg.org/ffmpeg.git cd ffmpeg ./configure --enable-shared --enable-swscale --enable-gpl --enable-libx264 --enable-libvpx --enable-libvorbis --enable-libtheora make -j `grep processor /proc/cpuinfo|wc -l` sudo make install sudo make install-libs ZoneMinder-1.32.2/utils/travis/run-tests.sh0000644000000000000000000000055713365153155017335 0ustar rootroot#!/bin/bash set -e set -o pipefail with_timestamps() { while read -r line; do echo -e "$(date +%T)\t$line"; done } run_tests() { mysql -uzmuser -pzmpass zm < ../../db/test.monitor.sql sudo zmu -l sudo zmc -m1 & sudo zma -m1 & sudo zmu -l sudo grep ERR /var/log/syslog sudo zmpkg.pl start sudo zmfilter.pl -f purgewhenfull } run_tests | with_timestamps ZoneMinder-1.32.2/utils/travis/bootstrap-zm.sh0000644000000000000000000000136713365153155020032 0ustar rootroot#!/bin/bash -x set -e set -o pipefail set -x with_timestamps() { while read -r line; do echo -e "$(date +%T)\t$line"; done } bootstrap_zm() { if [ "$ZM_BUILDMETHOD" = "autotools" ]; then libtoolize --force; fi if [ "$ZM_BUILDMETHOD" = "autotools" ]; then aclocal; fi if [ "$ZM_BUILDMETHOD" = "autotools" ]; then autoheader; fi if [ "$ZM_BUILDMETHOD" = "autotools" ]; then automake --force-missing --add-missing; fi if [ "$ZM_BUILDMETHOD" = "autotools" ]; then autoconf; fi mysql -uroot -e "CREATE DATABASE IF NOT EXISTS zm" mysql -uroot -e "GRANT ALL ON zm.* TO 'zmuser'@'localhost' IDENTIFIED BY 'zmpass'"; mysql -uroot -e "FLUSH PRIVILEGES" mysql -uzmuser -pzmpass < ${TRAVIS_BUILD_DIR}/db/zm_create.sql } bootstrap_zm | with_timestamps ZoneMinder-1.32.2/utils/travis/install-deps.sh0000644000000000000000000000121213365153155017755 0ustar rootroot#!/bin/bash set -e set -o pipefail with_timestamps() { while read -r line; do echo -e "$(date +%T)\t$line"; done } install_deps() { sudo apt-get update -qq sudo apt-get install -y -qq zlib1g-dev apache2 mysql-server php5 php5-mysql build-essential libmysqlclient-dev libssl-dev libbz2-dev libpcre3-dev libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm automake autoconf cmake libjpeg-turbo8-dev apache2-mpm-prefork libapache2-mod-php5 php5-cli libtheora-dev libvorbis-dev libvpx-dev libx264-dev 2>&1 > /dev/null } install_deps | with_timestamps ZoneMinder-1.32.2/utils/bump-version.pl0000755000000000000000000000134113365153155016503 0ustar rootroot#!/usr/bin/env perl # This script will bump the version number in any files listed in the below # @files array. It can only bump versions up, and does so by use of sed. use strict; use warnings; use Getopt::Long; my @files = ( "../version", "../configure.ac", "../CMakeLists.txt" ); my ($new, $current); open my $file, "../version" or die $!; chomp($current = <$file>); close $file; sub usage { print "Usage: bump-version.sh -n \n"; exit 1; } sub bump_version { foreach my $file (@files) { system("sed -i \"s/$current/$new/g\" $file"); } } GetOptions ("n=s" => \$new) or usage; usage if ! $new; die("New version ($new) is not greater than old version ($current)!") if ( $new le $current); bump_version; ZoneMinder-1.32.2/CHANGELOG.md0000644000000000000000000017343713365153155014206 0ustar rootroot# Change Log ## [v1.29.0](https://github.com/ZoneMinder/ZoneMinder/tree/v1.29.0) (2016-02-03) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.29.0-rc2...v1.29.0) **Merged pull requests:** - 1253 zms quit cmd [\#1254](https://github.com/ZoneMinder/ZoneMinder/pull/1254) ([pliablepixels](https://github.com/pliablepixels)) - Add Debug/Info lines reporting multi-server status [\#1252](https://github.com/ZoneMinder/ZoneMinder/pull/1252) ([connortechnology](https://github.com/connortechnology)) - Do debian package mods [\#1244](https://github.com/ZoneMinder/ZoneMinder/pull/1244) ([pliablepixels](https://github.com/pliablepixels)) - zmtrigger - process off+time delay condition [\#1240](https://github.com/ZoneMinder/ZoneMinder/pull/1240) ([knnniggett](https://github.com/knnniggett)) - Multi server [\#1233](https://github.com/ZoneMinder/ZoneMinder/pull/1233) ([connortechnology](https://github.com/connortechnology)) - remove Google open Sans external import [\#1232](https://github.com/ZoneMinder/ZoneMinder/pull/1232) ([connortechnology](https://github.com/connortechnology)) - PTZ - Added autostop to Down command on FI8918W [\#1189](https://github.com/ZoneMinder/ZoneMinder/pull/1189) ([marcolino7](https://github.com/marcolino7)) ## [v1.29.0-rc2](https://github.com/ZoneMinder/ZoneMinder/tree/v1.29.0-rc2) (2016-01-05) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.29.0-rc1...v1.29.0-rc2) **Merged pull requests:** - Multi server [\#1223](https://github.com/ZoneMinder/ZoneMinder/pull/1223) ([connortechnology](https://github.com/connortechnology)) - Multi server [\#1222](https://github.com/ZoneMinder/ZoneMinder/pull/1222) ([connortechnology](https://github.com/connortechnology)) - Multi server [\#1217](https://github.com/ZoneMinder/ZoneMinder/pull/1217) ([connortechnology](https://github.com/connortechnology)) - Multi server [\#1215](https://github.com/ZoneMinder/ZoneMinder/pull/1215) ([connortechnology](https://github.com/connortechnology)) - Change log updates [\#1172](https://github.com/ZoneMinder/ZoneMinder/pull/1172) ([SteveGilvarry](https://github.com/SteveGilvarry)) ## [v1.29.0-rc1](https://github.com/ZoneMinder/ZoneMinder/tree/v1.29.0-rc1) (2016-01-01) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.28.1...v1.29.0-rc1) **Merged pull requests:** - Bump version to 1.29.0 [\#1213](https://github.com/ZoneMinder/ZoneMinder/pull/1213) ([knnniggett](https://github.com/knnniggett)) - Missing rtd theme [\#1202](https://github.com/ZoneMinder/ZoneMinder/pull/1202) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Skip directories that have non-digits in them [\#1201](https://github.com/ZoneMinder/ZoneMinder/pull/1201) ([connortechnology](https://github.com/connortechnology)) - updated mobile app info [\#1200](https://github.com/ZoneMinder/ZoneMinder/pull/1200) ([pliablepixels](https://github.com/pliablepixels)) - Api more security [\#1196](https://github.com/ZoneMinder/ZoneMinder/pull/1196) ([pliablepixels](https://github.com/pliablepixels)) - Documentation [\#1194](https://github.com/ZoneMinder/ZoneMinder/pull/1194) ([pliablepixels](https://github.com/pliablepixels)) - Documentation updated for ubuntu [\#1193](https://github.com/ZoneMinder/ZoneMinder/pull/1193) ([pliablepixels](https://github.com/pliablepixels)) - Fixes \#1179 Libvlc Live555 Segmentation Fault [\#1190](https://github.com/ZoneMinder/ZoneMinder/pull/1190) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Add code to detect the change in REALM from older to newer firmware [\#1184](https://github.com/ZoneMinder/ZoneMinder/pull/1184) ([connortechnology](https://github.com/connortechnology)) - add a 1/8th scale option, which is useful for 1920x1080 streams [\#1182](https://github.com/ZoneMinder/ZoneMinder/pull/1182) ([connortechnology](https://github.com/connortechnology)) - Zms socket lock [\#1180](https://github.com/ZoneMinder/ZoneMinder/pull/1180) ([connortechnology](https://github.com/connortechnology)) - Check for the presence of CrudControllerTrait.php instead of .git [\#1178](https://github.com/ZoneMinder/ZoneMinder/pull/1178) ([knnniggett](https://github.com/knnniggett)) - Partial fix for \#1167 [\#1176](https://github.com/ZoneMinder/ZoneMinder/pull/1176) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Error on missing submodules [\#1173](https://github.com/ZoneMinder/ZoneMinder/pull/1173) ([knnniggett](https://github.com/knnniggett)) - fix mem alloc fault in zm\_monitor.cpp [\#1168](https://github.com/ZoneMinder/ZoneMinder/pull/1168) ([knnniggett](https://github.com/knnniggett)) - compat for php 5.4 [\#1164](https://github.com/ZoneMinder/ZoneMinder/pull/1164) ([abishai](https://github.com/abishai)) - remove comment at end of line [\#1157](https://github.com/ZoneMinder/ZoneMinder/pull/1157) ([connortechnology](https://github.com/connortechnology)) - Reorder RTSPDescribe to avoid -wreorder warnings [\#1147](https://github.com/ZoneMinder/ZoneMinder/pull/1147) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Update to \#1137 for backward compatibility. [\#1142](https://github.com/ZoneMinder/ZoneMinder/pull/1142) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Update Travis to ffmpeg 2.8.1 for testing [\#1139](https://github.com/ZoneMinder/ZoneMinder/pull/1139) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Replace deprecated FFmpeg API [\#1137](https://github.com/ZoneMinder/ZoneMinder/pull/1137) ([onlyjob](https://github.com/onlyjob)) - added prev/next event ids [\#1136](https://github.com/ZoneMinder/ZoneMinder/pull/1136) ([pliablepixels](https://github.com/pliablepixels)) - Install nph-zms with FILES so it is listed in install\_manifest.txt [\#1124](https://github.com/ZoneMinder/ZoneMinder/pull/1124) ([baffo32](https://github.com/baffo32)) - Stray semicolon causes SQL error [\#1123](https://github.com/ZoneMinder/ZoneMinder/pull/1123) ([baffo32](https://github.com/baffo32)) - Use relative URL's instead of absolute [\#1121](https://github.com/ZoneMinder/ZoneMinder/pull/1121) ([knnniggett](https://github.com/knnniggett)) - Update version check URL [\#1120](https://github.com/ZoneMinder/ZoneMinder/pull/1120) ([kylejohnson](https://github.com/kylejohnson)) - Add index to frames [\#1116](https://github.com/ZoneMinder/ZoneMinder/pull/1116) ([kylejohnson](https://github.com/kylejohnson)) - Fatal if content dirs are unwritable [\#1113](https://github.com/ZoneMinder/ZoneMinder/pull/1113) ([baffo32](https://github.com/baffo32)) - Fatal error if date.timezone is unset [\#1111](https://github.com/ZoneMinder/ZoneMinder/pull/1111) ([baffo32](https://github.com/baffo32)) - Fix faulty zm.conf.new install line [\#1107](https://github.com/ZoneMinder/ZoneMinder/pull/1107) ([baffo32](https://github.com/baffo32)) - Update preinst [\#1103](https://github.com/ZoneMinder/ZoneMinder/pull/1103) ([seebaer1976](https://github.com/seebaer1976)) - Update apache.conf [\#1102](https://github.com/ZoneMinder/ZoneMinder/pull/1102) ([seebaer1976](https://github.com/seebaer1976)) - Update rules [\#1101](https://github.com/ZoneMinder/ZoneMinder/pull/1101) ([seebaer1976](https://github.com/seebaer1976)) - Update links [\#1100](https://github.com/ZoneMinder/ZoneMinder/pull/1100) ([seebaer1976](https://github.com/seebaer1976)) - Update preinst [\#1099](https://github.com/ZoneMinder/ZoneMinder/pull/1099) ([seebaer1976](https://github.com/seebaer1976)) - Fix zmaudit [\#1095](https://github.com/ZoneMinder/ZoneMinder/pull/1095) ([connortechnology](https://github.com/connortechnology)) - fixed version compare logic [\#1094](https://github.com/ZoneMinder/ZoneMinder/pull/1094) ([pliablepixels](https://github.com/pliablepixels)) - Don't install zm.conf if it already exists [\#1090](https://github.com/ZoneMinder/ZoneMinder/pull/1090) ([connortechnology](https://github.com/connortechnology)) - change types and move things around to remove compile warnings [\#1089](https://github.com/ZoneMinder/ZoneMinder/pull/1089) ([connortechnology](https://github.com/connortechnology)) - Tz [\#1084](https://github.com/ZoneMinder/ZoneMinder/pull/1084) ([connortechnology](https://github.com/connortechnology)) - fixed orange display for monitor mode [\#1083](https://github.com/ZoneMinder/ZoneMinder/pull/1083) ([pliablepixels](https://github.com/pliablepixels)) - use deleteAll\(\) vs. delete\(\) when deleting an Event's Frames [\#1080](https://github.com/ZoneMinder/ZoneMinder/pull/1080) ([kylejohnson](https://github.com/kylejohnson)) - Added control script for SunEyes SP-P1802SWPTZ [\#1079](https://github.com/ZoneMinder/ZoneMinder/pull/1079) ([bofhdk](https://github.com/bofhdk)) - Use the 3.0 branch of crud, compatible with cakephp 2.x [\#1078](https://github.com/ZoneMinder/ZoneMinder/pull/1078) ([kylejohnson](https://github.com/kylejohnson)) - 663 frames primary key [\#1075](https://github.com/ZoneMinder/ZoneMinder/pull/1075) ([kylejohnson](https://github.com/kylejohnson)) - Delete fixes for Events [\#1073](https://github.com/ZoneMinder/ZoneMinder/pull/1073) ([pliablepixels](https://github.com/pliablepixels)) - restart monitor when edited via APIs [\#1070](https://github.com/ZoneMinder/ZoneMinder/pull/1070) ([pliablepixels](https://github.com/pliablepixels)) - add debug statements for when LastWriteTime is not defined. [\#1067](https://github.com/ZoneMinder/ZoneMinder/pull/1067) ([connortechnology](https://github.com/connortechnology)) - fixed recaptcha showing up pre DB update [\#1066](https://github.com/ZoneMinder/ZoneMinder/pull/1066) ([pliablepixels](https://github.com/pliablepixels)) - fixed security instructions for curl [\#1062](https://github.com/ZoneMinder/ZoneMinder/pull/1062) ([pliablepixels](https://github.com/pliablepixels)) - header typo corrections [\#1058](https://github.com/ZoneMinder/ZoneMinder/pull/1058) ([onlyjob](https://github.com/onlyjob)) - quick fix for \#1055: make sure our mmap fd is \> 2 [\#1057](https://github.com/ZoneMinder/ZoneMinder/pull/1057) ([connortechnology](https://github.com/connortechnology)) - Fix sgfault caused by the privacy mask stuff [\#1056](https://github.com/ZoneMinder/ZoneMinder/pull/1056) ([connortechnology](https://github.com/connortechnology)) - link to cambozola package, rather than download during build [\#1054](https://github.com/ZoneMinder/ZoneMinder/pull/1054) ([knnniggett](https://github.com/knnniggett)) - redhat rpm packaging modifications [\#1052](https://github.com/ZoneMinder/ZoneMinder/pull/1052) ([knnniggett](https://github.com/knnniggett)) - remove core.php, modify core.php.default [\#1049](https://github.com/ZoneMinder/ZoneMinder/pull/1049) ([knnniggett](https://github.com/knnniggett)) - Google recaptcha [\#1048](https://github.com/ZoneMinder/ZoneMinder/pull/1048) ([pliablepixels](https://github.com/pliablepixels)) - enable/disable RTSP Describe Header [\#1045](https://github.com/ZoneMinder/ZoneMinder/pull/1045) ([knnniggett](https://github.com/knnniggett)) - Add Documentation for Privacy zones [\#1044](https://github.com/ZoneMinder/ZoneMinder/pull/1044) ([schrorg](https://github.com/schrorg)) - added note about potential Perl and PHP time translation conflict witโ€ฆ [\#1043](https://github.com/ZoneMinder/ZoneMinder/pull/1043) ([pliablepixels](https://github.com/pliablepixels)) - Multi server [\#1040](https://github.com/ZoneMinder/ZoneMinder/pull/1040) ([connortechnology](https://github.com/connortechnology)) - 1038 fixing state mgmt 1030 is active fix [\#1039](https://github.com/ZoneMinder/ZoneMinder/pull/1039) ([pliablepixels](https://github.com/pliablepixels)) - Grey color for disabled buttons [\#1037](https://github.com/ZoneMinder/ZoneMinder/pull/1037) ([pliablepixels](https://github.com/pliablepixels)) - Update filterevents.rst [\#1035](https://github.com/ZoneMinder/ZoneMinder/pull/1035) ([tikismoke](https://github.com/tikismoke)) - add warning and help text for maxfps fields [\#1033](https://github.com/ZoneMinder/ZoneMinder/pull/1033) ([knnniggett](https://github.com/knnniggett)) - update doc [\#1032](https://github.com/ZoneMinder/ZoneMinder/pull/1032) ([tikismoke](https://github.com/tikismoke)) - Remove full path from Logger filename [\#1029](https://github.com/ZoneMinder/ZoneMinder/pull/1029) ([knnniggett](https://github.com/knnniggett)) - Typo in README.md [\#1027](https://github.com/ZoneMinder/ZoneMinder/pull/1027) ([tikismoke](https://github.com/tikismoke)) - Add new zone type - privacy zones [\#1026](https://github.com/ZoneMinder/ZoneMinder/pull/1026) ([schrorg](https://github.com/schrorg)) - Send login activity to the zoneminder event log [\#1021](https://github.com/ZoneMinder/ZoneMinder/pull/1021) ([knnniggett](https://github.com/knnniggett)) - Small dark CSS fixes in frames and timeline view [\#1019](https://github.com/ZoneMinder/ZoneMinder/pull/1019) ([schrorg](https://github.com/schrorg)) - New User Permission "Groups" [\#1018](https://github.com/ZoneMinder/ZoneMinder/pull/1018) ([knnniggett](https://github.com/knnniggett)) - 1013 document migration [\#1017](https://github.com/ZoneMinder/ZoneMinder/pull/1017) ([pliablepixels](https://github.com/pliablepixels)) - Fix issue with score values less than 0 [\#1016](https://github.com/ZoneMinder/ZoneMinder/pull/1016) ([knnniggett](https://github.com/knnniggett)) - Explained a caveat with using relative times [\#1012](https://github.com/ZoneMinder/ZoneMinder/pull/1012) ([pliablepixels](https://github.com/pliablepixels)) - Included logic to not enforce authentication in API layer if ZM auth is off [\#1008](https://github.com/ZoneMinder/ZoneMinder/pull/1008) ([pliablepixels](https://github.com/pliablepixels)) - Update to ffmpeg 2.7.2 in travis build [\#1007](https://github.com/ZoneMinder/ZoneMinder/pull/1007) ([SteveGilvarry](https://github.com/SteveGilvarry)) - I was using the wrong field to check for portal authentication [\#1006](https://github.com/ZoneMinder/ZoneMinder/pull/1006) ([pliablepixels](https://github.com/pliablepixels)) - Demote user auth info message to debug [\#1003](https://github.com/ZoneMinder/ZoneMinder/pull/1003) ([Linwood-F](https://github.com/Linwood-F)) - Add scale as optional feature to image.php [\#1001](https://github.com/ZoneMinder/ZoneMinder/pull/1001) ([Linwood-F](https://github.com/Linwood-F)) - 995 events count per api \(bumps up \# of events reported per API call\) [\#996](https://github.com/ZoneMinder/ZoneMinder/pull/996) ([pliablepixels](https://github.com/pliablepixels)) - APIs will be served only if user is logged into the ZM portal [\#994](https://github.com/ZoneMinder/ZoneMinder/pull/994) ([pliablepixels](https://github.com/pliablepixels)) - Add option to make TimeStamp larger [\#992](https://github.com/ZoneMinder/ZoneMinder/pull/992) ([schrorg](https://github.com/schrorg)) - Implemented \#989 \(highlight current row in tables\) for dark CSS [\#990](https://github.com/ZoneMinder/ZoneMinder/pull/990) ([schrorg](https://github.com/schrorg)) - CSS\[skins/classic\]: highlight current row in tables. [\#989](https://github.com/ZoneMinder/ZoneMinder/pull/989) ([onlyjob](https://github.com/onlyjob)) - quiet error when no Servers in Servers table [\#986](https://github.com/ZoneMinder/ZoneMinder/pull/986) ([connortechnology](https://github.com/connortechnology)) - fix \#948 1 [\#985](https://github.com/ZoneMinder/ZoneMinder/pull/985) ([connortechnology](https://github.com/connortechnology)) - Remove shared data warning for purpose query only [\#984](https://github.com/ZoneMinder/ZoneMinder/pull/984) ([Linwood-F](https://github.com/Linwood-F)) - Change from info to debug [\#983](https://github.com/ZoneMinder/ZoneMinder/pull/983) ([Linwood-F](https://github.com/Linwood-F)) - Fix image dimensions check [\#980](https://github.com/ZoneMinder/ZoneMinder/pull/980) ([connortechnology](https://github.com/connortechnology)) - Apache.conf modifications [\#968](https://github.com/ZoneMinder/ZoneMinder/pull/968) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Dark CSS for classic theme [\#967](https://github.com/ZoneMinder/ZoneMinder/pull/967) ([schrorg](https://github.com/schrorg)) - Auto generated changelog [\#966](https://github.com/ZoneMinder/ZoneMinder/pull/966) ([SteveGilvarry](https://github.com/SteveGilvarry)) - 959 add exif date time to images [\#962](https://github.com/ZoneMinder/ZoneMinder/pull/962) ([Linwood-F](https://github.com/Linwood-F)) - Add analysis interval parameter to monitors settings [\#956](https://github.com/ZoneMinder/ZoneMinder/pull/956) ([manupap1](https://github.com/manupap1)) - Fixed Configs API to return all values [\#955](https://github.com/ZoneMinder/ZoneMinder/pull/955) ([pliablepixels](https://github.com/pliablepixels)) - Change encoding of german language file to UTF-8 [\#952](https://github.com/ZoneMinder/ZoneMinder/pull/952) ([schrorg](https://github.com/schrorg)) - Show correct part of URL \(hostname\) for ffmpeg sources in console [\#951](https://github.com/ZoneMinder/ZoneMinder/pull/951) ([schrorg](https://github.com/schrorg)) - add php-gd to list of dependencies for debian and ubuntu builds [\#944](https://github.com/ZoneMinder/ZoneMinder/pull/944) ([connortechnology](https://github.com/connortechnology)) - rpm packaging - require php-gd [\#943](https://github.com/ZoneMinder/ZoneMinder/pull/943) ([knnniggett](https://github.com/knnniggett)) - add some utility db functions [\#942](https://github.com/ZoneMinder/ZoneMinder/pull/942) ([connortechnology](https://github.com/connortechnology)) - add space instead of + to handle old Axis cameras [\#941](https://github.com/ZoneMinder/ZoneMinder/pull/941) ([connortechnology](https://github.com/connortechnology)) - zmtrigger: POD documentation [\#938](https://github.com/ZoneMinder/ZoneMinder/pull/938) ([onlyjob](https://github.com/onlyjob)) - improve log [\#937](https://github.com/ZoneMinder/ZoneMinder/pull/937) ([connortechnology](https://github.com/connortechnology)) - add error handling on failure to open serial port [\#936](https://github.com/ZoneMinder/ZoneMinder/pull/936) ([connortechnology](https://github.com/connortechnology)) - fix utf8 ' characters [\#934](https://github.com/ZoneMinder/ZoneMinder/pull/934) ([connortechnology](https://github.com/connortechnology)) - roudn up when calculating buffer size for scaled image. Fixes \#932 [\#933](https://github.com/ZoneMinder/ZoneMinder/pull/933) ([connortechnology](https://github.com/connortechnology)) - Added API routing [\#931](https://github.com/ZoneMinder/ZoneMinder/pull/931) ([pliablepixels](https://github.com/pliablepixels)) - don't include .cpp in man [\#930](https://github.com/ZoneMinder/ZoneMinder/pull/930) ([connortechnology](https://github.com/connortechnology)) - fix pod2man generation for out-of-source builds [\#928](https://github.com/ZoneMinder/ZoneMinder/pull/928) ([knnniggett](https://github.com/knnniggett)) - Version to 1.28.99 [\#926](https://github.com/ZoneMinder/ZoneMinder/pull/926) ([connortechnology](https://github.com/connortechnology)) - Introduce a read\_into function in the Buffer. [\#923](https://github.com/ZoneMinder/ZoneMinder/pull/923) ([connortechnology](https://github.com/connortechnology)) - Added "RewriteBase /zm/api" for API routing [\#921](https://github.com/ZoneMinder/ZoneMinder/pull/921) ([pliablepixels](https://github.com/pliablepixels)) - Zms no crash [\#920](https://github.com/ZoneMinder/ZoneMinder/pull/920) ([connortechnology](https://github.com/connortechnology)) - add check for gettime in librt, needed for building on pi [\#919](https://github.com/ZoneMinder/ZoneMinder/pull/919) ([connortechnology](https://github.com/connortechnology)) - Add ServerId to Monitors [\#918](https://github.com/ZoneMinder/ZoneMinder/pull/918) ([connortechnology](https://github.com/connortechnology)) - Dumb down Crud from 4.0 -\> 3.0.10 [\#915](https://github.com/ZoneMinder/ZoneMinder/pull/915) ([knnniggett](https://github.com/knnniggett)) - Add Servers Table and add Id PRIMARY KEY to States [\#910](https://github.com/ZoneMinder/ZoneMinder/pull/910) ([connortechnology](https://github.com/connortechnology)) - fix montage view issue in mobile skin [\#909](https://github.com/ZoneMinder/ZoneMinder/pull/909) ([knnniggett](https://github.com/knnniggett)) - Solaris cmake [\#906](https://github.com/ZoneMinder/ZoneMinder/pull/906) ([knnniggett](https://github.com/knnniggett)) - Fix el7 build [\#902](https://github.com/ZoneMinder/ZoneMinder/pull/902) ([bill-mcgonigle](https://github.com/bill-mcgonigle)) - 898 is running states [\#899](https://github.com/ZoneMinder/ZoneMinder/pull/899) ([pliablepixels](https://github.com/pliablepixels)) - Fixed events API to remove thumbnail code [\#897](https://github.com/ZoneMinder/ZoneMinder/pull/897) ([pliablepixels](https://github.com/pliablepixels)) - Generate man pages for perl scripts & C Binaries in the bin folder [\#896](https://github.com/ZoneMinder/ZoneMinder/pull/896) ([knnniggett](https://github.com/knnniggett)) - 893 foscam 9831 w and other foscams [\#895](https://github.com/ZoneMinder/ZoneMinder/pull/895) ([pliablepixels](https://github.com/pliablepixels)) - 893 foscam 9831 w and other foscams [\#894](https://github.com/ZoneMinder/ZoneMinder/pull/894) ([pliablepixels](https://github.com/pliablepixels)) - Zmwatch cleanup2 [\#891](https://github.com/ZoneMinder/ZoneMinder/pull/891) ([connortechnology](https://github.com/connortechnology)) - reverse the if statement to reduce indenting [\#890](https://github.com/ZoneMinder/ZoneMinder/pull/890) ([connortechnology](https://github.com/connortechnology)) - Updated API document [\#886](https://github.com/ZoneMinder/ZoneMinder/pull/886) ([pliablepixels](https://github.com/pliablepixels)) - Use avconv as alternative to ffmpeg executable [\#884](https://github.com/ZoneMinder/ZoneMinder/pull/884) ([SteveGilvarry](https://github.com/SteveGilvarry)) - 881 bootstrap loading config [\#883](https://github.com/ZoneMinder/ZoneMinder/pull/883) ([pliablepixels](https://github.com/pliablepixels)) - Merged Angular UI branch API to master [\#882](https://github.com/ZoneMinder/ZoneMinder/pull/882) ([pliablepixels](https://github.com/pliablepixels)) - Add version to the startup log line [\#875](https://github.com/ZoneMinder/ZoneMinder/pull/875) ([connortechnology](https://github.com/connortechnology)) - German translation update [\#874](https://github.com/ZoneMinder/ZoneMinder/pull/874) ([seeebek](https://github.com/seeebek)) - reduce the wait to 2/10ths instead of a whole second [\#873](https://github.com/ZoneMinder/ZoneMinder/pull/873) ([connortechnology](https://github.com/connortechnology)) - alter the logic of ReadData. New behaviour is documented. [\#870](https://github.com/ZoneMinder/ZoneMinder/pull/870) ([connortechnology](https://github.com/connortechnology)) - analysis optimisations [\#867](https://github.com/ZoneMinder/ZoneMinder/pull/867) ([connortechnology](https://github.com/connortechnology)) - Don't die if db goes away during logging [\#866](https://github.com/ZoneMinder/ZoneMinder/pull/866) ([connortechnology](https://github.com/connortechnology)) - Move iostream inclusion in zm.h and declare explicitly the namespace [\#859](https://github.com/ZoneMinder/ZoneMinder/pull/859) ([manupap1](https://github.com/manupap1)) - Fix detection of deprecated libav / ffmpeg functions [\#858](https://github.com/ZoneMinder/ZoneMinder/pull/858) ([manupap1](https://github.com/manupap1)) - Correct bareword config entries with newer {} style [\#856](https://github.com/ZoneMinder/ZoneMinder/pull/856) ([connortechnology](https://github.com/connortechnology)) - update german translation [\#854](https://github.com/ZoneMinder/ZoneMinder/pull/854) ([seeebek](https://github.com/seeebek)) - ubuntu 15.04 [\#850](https://github.com/ZoneMinder/ZoneMinder/pull/850) ([seeebek](https://github.com/seeebek)) - faster shutdown [\#847](https://github.com/ZoneMinder/ZoneMinder/pull/847) ([connortechnology](https://github.com/connortechnology)) - Additional SLANG changes [\#845](https://github.com/ZoneMinder/ZoneMinder/pull/845) ([knnniggett](https://github.com/knnniggett)) - Cmake hostos [\#844](https://github.com/ZoneMinder/ZoneMinder/pull/844) ([knnniggett](https://github.com/knnniggett)) - Port to OmniOS/Solaris [\#842](https://github.com/ZoneMinder/ZoneMinder/pull/842) ([whorfin](https://github.com/whorfin)) - Zmaudit update1: Make MIN\_AGE Configurable [\#838](https://github.com/ZoneMinder/ZoneMinder/pull/838) ([connortechnology](https://github.com/connortechnology)) - Update to FI982821W\_Y2k [\#836](https://github.com/ZoneMinder/ZoneMinder/pull/836) ([connortechnology](https://github.com/connortechnology)) - add translate function [\#833](https://github.com/ZoneMinder/ZoneMinder/pull/833) ([knnniggett](https://github.com/knnniggett)) - Separate css window sizes [\#829](https://github.com/ZoneMinder/ZoneMinder/pull/829) ([connortechnology](https://github.com/connortechnology)) - Fix fast forward/reverse in event playback \(\#688\) [\#825](https://github.com/ZoneMinder/ZoneMinder/pull/825) ([rwg0](https://github.com/rwg0)) - Fix: typo in options\_libvlc [\#824](https://github.com/ZoneMinder/ZoneMinder/pull/824) ([Lihis](https://github.com/Lihis)) - close the session before requiring the page contents to fix the concurre... [\#823](https://github.com/ZoneMinder/ZoneMinder/pull/823) ([connortechnology](https://github.com/connortechnology)) - Fix build issues on kFreeBSD. Fixes \#771 [\#822](https://github.com/ZoneMinder/ZoneMinder/pull/822) ([connortechnology](https://github.com/connortechnology)) - beautifying \*.pm [\#821](https://github.com/ZoneMinder/ZoneMinder/pull/821) ([onlyjob](https://github.com/onlyjob)) - Remove hardcoded localized strings in php files and update lang files [\#820](https://github.com/ZoneMinder/ZoneMinder/pull/820) ([manupap1](https://github.com/manupap1)) - Fix french lang file [\#818](https://github.com/ZoneMinder/ZoneMinder/pull/818) ([manupap1](https://github.com/manupap1)) - more perlcritic/PBP corrections [\#816](https://github.com/ZoneMinder/ZoneMinder/pull/816) ([onlyjob](https://github.com/onlyjob)) - last batch of POD and readability conversions for \*.pl scripts [\#815](https://github.com/ZoneMinder/ZoneMinder/pull/815) ([onlyjob](https://github.com/onlyjob)) - Fixes \#760 in part Clean up CMakeLists.txt [\#812](https://github.com/ZoneMinder/ZoneMinder/pull/812) ([SteveGilvarry](https://github.com/SteveGilvarry)) - upgrade bundled jQuery \(Closes: \#785\) [\#809](https://github.com/ZoneMinder/ZoneMinder/pull/809) ([onlyjob](https://github.com/onlyjob)) - Update Mootools [\#803](https://github.com/ZoneMinder/ZoneMinder/pull/803) ([knnniggett](https://github.com/knnniggett)) - hide USE\_DEEP\_STORAGE [\#802](https://github.com/ZoneMinder/ZoneMinder/pull/802) ([knnniggett](https://github.com/knnniggett)) - link zms to nph-zms, rather than build identical [\#801](https://github.com/ZoneMinder/ZoneMinder/pull/801) ([knnniggett](https://github.com/knnniggett)) - \* use pthread\_join instead of pthread\_tryjoin\_np [\#800](https://github.com/ZoneMinder/ZoneMinder/pull/800) ([Sune1337](https://github.com/Sune1337)) - zmcontrol.pl, zmfilter.pl: pod2usage + readability improvements. [\#798](https://github.com/ZoneMinder/ZoneMinder/pull/798) ([onlyjob](https://github.com/onlyjob)) - one small fix for a log line where the 4th parameter wasn't included. T... [\#796](https://github.com/ZoneMinder/ZoneMinder/pull/796) ([connortechnology](https://github.com/connortechnology)) - zmaudit.pl, zmcamtool.pl: pod2usage, PBP/5 + readability [\#795](https://github.com/ZoneMinder/ZoneMinder/pull/795) ([onlyjob](https://github.com/onlyjob)) - as discussed... [\#794](https://github.com/ZoneMinder/ZoneMinder/pull/794) ([onlyjob](https://github.com/onlyjob)) - Leftover short open tags [\#793](https://github.com/ZoneMinder/ZoneMinder/pull/793) ([SteveGilvarry](https://github.com/SteveGilvarry)) - cmake - use perl INSTALLDIRS [\#792](https://github.com/ZoneMinder/ZoneMinder/pull/792) ([knnniggett](https://github.com/knnniggett)) - \#783 - related corrections [\#791](https://github.com/ZoneMinder/ZoneMinder/pull/791) ([onlyjob](https://github.com/onlyjob)) - skins/classic: fix HTML export with USE\_DEEP\_STORAGE \(Closes: \#506\). [\#782](https://github.com/ZoneMinder/ZoneMinder/pull/782) ([onlyjob](https://github.com/onlyjob)) - Check for libv4l1-videodev headers [\#781](https://github.com/ZoneMinder/ZoneMinder/pull/781) ([knnniggett](https://github.com/knnniggett)) - build: add PATH\_MAX definitions \(needed on GNU Hurd\) [\#778](https://github.com/ZoneMinder/ZoneMinder/pull/778) ([onlyjob](https://github.com/onlyjob)) - Freebsd fixes [\#775](https://github.com/ZoneMinder/ZoneMinder/pull/775) ([connortechnology](https://github.com/connortechnology)) - Use tmpfiles.d to manage tmpdir and sockdir [\#774](https://github.com/ZoneMinder/ZoneMinder/pull/774) ([knnniggett](https://github.com/knnniggett)) - Don't trigger linked cameras on new events [\#772](https://github.com/ZoneMinder/ZoneMinder/pull/772) ([balr0g](https://github.com/balr0g)) - POD: zmupdate.pl converted to "pod2usage" [\#763](https://github.com/ZoneMinder/ZoneMinder/pull/763) ([onlyjob](https://github.com/onlyjob)) - build: fix FTBFS with format-hardening \(please review\) [\#761](https://github.com/ZoneMinder/ZoneMinder/pull/761) ([onlyjob](https://github.com/onlyjob)) - fixing POD errors [\#759](https://github.com/ZoneMinder/ZoneMinder/pull/759) ([onlyjob](https://github.com/onlyjob)) - Ignore autogenerated files in git [\#746](https://github.com/ZoneMinder/ZoneMinder/pull/746) ([manupap1](https://github.com/manupap1)) - when auth is needed, try command again before dying. [\#739](https://github.com/ZoneMinder/ZoneMinder/pull/739) ([connortechnology](https://github.com/connortechnology)) - remove NETPBM dependency from autotools [\#737](https://github.com/ZoneMinder/ZoneMinder/pull/737) ([knnniggett](https://github.com/knnniggett)) - fix extra slash when adding trackurl to controlurl [\#732](https://github.com/ZoneMinder/ZoneMinder/pull/732) ([connortechnology](https://github.com/connortechnology)) - Fix image and css import paths for style/skin named "flat" [\#730](https://github.com/ZoneMinder/ZoneMinder/pull/730) ([ljack](https://github.com/ljack)) - Update control.css [\#729](https://github.com/ZoneMinder/ZoneMinder/pull/729) ([ljack](https://github.com/ljack)) - Fix event view [\#728](https://github.com/ZoneMinder/ZoneMinder/pull/728) ([connortechnology](https://github.com/connortechnology)) - User selectable arp tool [\#723](https://github.com/ZoneMinder/ZoneMinder/pull/723) ([knnniggett](https://github.com/knnniggett)) - remove unneeded files [\#722](https://github.com/ZoneMinder/ZoneMinder/pull/722) ([knnniggett](https://github.com/knnniggett)) - add onvif ptz control into update script [\#721](https://github.com/ZoneMinder/ZoneMinder/pull/721) ([knnniggett](https://github.com/knnniggett)) - Don't show ONVIf probe link when ONVIF support is not enabled [\#720](https://github.com/ZoneMinder/ZoneMinder/pull/720) ([knnniggett](https://github.com/knnniggett)) - Allow zm to build w/o ffmpeg [\#719](https://github.com/ZoneMinder/ZoneMinder/pull/719) ([knnniggett](https://github.com/knnniggett)) - Removed el6 from endif arguments [\#718](https://github.com/ZoneMinder/ZoneMinder/pull/718) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Update fr\_fr.php [\#714](https://github.com/ZoneMinder/ZoneMinder/pull/714) ([Jypy](https://github.com/Jypy)) - Check to make sure that skin and css are valid. [\#713](https://github.com/ZoneMinder/ZoneMinder/pull/713) ([connortechnology](https://github.com/connortechnology)) - Fixes \#710 Added libavformat version check around free context functions [\#711](https://github.com/ZoneMinder/ZoneMinder/pull/711) ([SteveGilvarry](https://github.com/SteveGilvarry)) - try harder to find arp. [\#709](https://github.com/ZoneMinder/ZoneMinder/pull/709) ([connortechnology](https://github.com/connortechnology)) - Make el6 and el7 build process a little more automated [\#704](https://github.com/ZoneMinder/ZoneMinder/pull/704) ([clipo1979](https://github.com/clipo1979)) - small improvements: [\#702](https://github.com/ZoneMinder/ZoneMinder/pull/702) ([connortechnology](https://github.com/connortechnology)) - Centos 7 rpm packaging [\#700](https://github.com/ZoneMinder/ZoneMinder/pull/700) ([knnniggett](https://github.com/knnniggett)) - tmpfile.conf for systemd [\#699](https://github.com/ZoneMinder/ZoneMinder/pull/699) ([clipo1979](https://github.com/clipo1979)) - Improve delete event [\#696](https://github.com/ZoneMinder/ZoneMinder/pull/696) ([connortechnology](https://github.com/connortechnology)) - process RTSP DESCRIBE response header [\#687](https://github.com/ZoneMinder/ZoneMinder/pull/687) ([knnniggett](https://github.com/knnniggett)) ## [v1.28.1](https://github.com/ZoneMinder/ZoneMinder/tree/v1.28.1) (2015-02-05) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.28.0...v1.28.1) **Merged pull requests:** - fix content-type parsing when there are options on it [\#692](https://github.com/ZoneMinder/ZoneMinder/pull/692) ([connortechnology](https://github.com/connortechnology)) - this fixes Digest Auth for the mjpeg stream on a TV-IP302PI [\#691](https://github.com/ZoneMinder/ZoneMinder/pull/691) ([connortechnology](https://github.com/connortechnology)) - small performance improvement when streaming. [\#675](https://github.com/ZoneMinder/ZoneMinder/pull/675) ([connortechnology](https://github.com/connortechnology)) - Kill zmcontrol [\#666](https://github.com/ZoneMinder/ZoneMinder/pull/666) ([connortechnology](https://github.com/connortechnology)) - Don't fail if an unexpected rtp packet type is received [\#665](https://github.com/ZoneMinder/ZoneMinder/pull/665) ([knnniggett](https://github.com/knnniggett)) - Versions command line args [\#664](https://github.com/ZoneMinder/ZoneMinder/pull/664) ([connortechnology](https://github.com/connortechnology)) - Update et\_ee.php [\#662](https://github.com/ZoneMinder/ZoneMinder/pull/662) ([hanzese](https://github.com/hanzese)) - \#658 Fix error message for finding arp path [\#660](https://github.com/ZoneMinder/ZoneMinder/pull/660) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Make the log export use ZM\_PATH\_SWAP and report the full path on error [\#657](https://github.com/ZoneMinder/ZoneMinder/pull/657) ([connortechnology](https://github.com/connortechnology)) - replace getLoad\(\) / getDiskPercent\(\) with PHP native functions [\#654](https://github.com/ZoneMinder/ZoneMinder/pull/654) ([lifeofguenter](https://github.com/lifeofguenter)) - Modified zmfilter.pl.in to fix \#652, crashing while processing backgroun... [\#653](https://github.com/ZoneMinder/ZoneMinder/pull/653) ([thebostik](https://github.com/thebostik)) - Remove no longer needed patch because applied to master [\#651](https://github.com/ZoneMinder/ZoneMinder/pull/651) ([manupap1](https://github.com/manupap1)) - Don't check for zmdc.pl when stopping via systemd [\#647](https://github.com/ZoneMinder/ZoneMinder/pull/647) ([ariscop](https://github.com/ariscop)) - Split the debian package into several packages [\#646](https://github.com/ZoneMinder/ZoneMinder/pull/646) ([manupap1](https://github.com/manupap1)) - Skin css default [\#645](https://github.com/ZoneMinder/ZoneMinder/pull/645) ([connortechnology](https://github.com/connortechnology)) - Offer login prompt instead of throwing error [\#640](https://github.com/ZoneMinder/ZoneMinder/pull/640) ([jrd288](https://github.com/jrd288)) - zmfilter: Send message for events that are still ongoing [\#638](https://github.com/ZoneMinder/ZoneMinder/pull/638) ([KristofRobot](https://github.com/KristofRobot)) - Some fixes to the debian folder [\#636](https://github.com/ZoneMinder/ZoneMinder/pull/636) ([manupap1](https://github.com/manupap1)) - Improve zmcontrol.pl [\#635](https://github.com/ZoneMinder/ZoneMinder/pull/635) ([connortechnology](https://github.com/connortechnology)) - Add debconf / dbconfig support to debian8 folder [\#634](https://github.com/ZoneMinder/ZoneMinder/pull/634) ([manupap1](https://github.com/manupap1)) - better fix for the view=console security flaw. [\#632](https://github.com/ZoneMinder/ZoneMinder/pull/632) ([connortechnology](https://github.com/connortechnology)) - add check to see if user has rights to view this monitor [\#631](https://github.com/ZoneMinder/ZoneMinder/pull/631) ([connortechnology](https://github.com/connortechnology)) - fix auth requirement on view=console by checking for user when AUTH is on [\#628](https://github.com/ZoneMinder/ZoneMinder/pull/628) ([connortechnology](https://github.com/connortechnology)) - Output to stderror when zmu can't read zm.conf [\#627](https://github.com/ZoneMinder/ZoneMinder/pull/627) ([knnniggett](https://github.com/knnniggett)) - Add missing dependency to policykit-1 [\#621](https://github.com/ZoneMinder/ZoneMinder/pull/621) ([manupap1](https://github.com/manupap1)) - Replace PHP Short Open Tags - Fixes \#11 [\#620](https://github.com/ZoneMinder/ZoneMinder/pull/620) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Rtsp [\#615](https://github.com/ZoneMinder/ZoneMinder/pull/615) ([knnniggett](https://github.com/knnniggett)) - Merge flat css to classic [\#614](https://github.com/ZoneMinder/ZoneMinder/pull/614) ([connortechnology](https://github.com/connortechnology)) - echo the URL to the RTSP device during the OPTIONS directive [\#608](https://github.com/ZoneMinder/ZoneMinder/pull/608) ([knnniggett](https://github.com/knnniggett)) - Fix some memory leaks in zma [\#607](https://github.com/ZoneMinder/ZoneMinder/pull/607) ([manupap1](https://github.com/manupap1)) - Fix a mismatched free in zmc binary [\#606](https://github.com/ZoneMinder/ZoneMinder/pull/606) ([manupap1](https://github.com/manupap1)) - New debian folder for jessie release [\#605](https://github.com/ZoneMinder/ZoneMinder/pull/605) ([manupap1](https://github.com/manupap1)) - Css skins for classic [\#602](https://github.com/ZoneMinder/ZoneMinder/pull/602) ([connortechnology](https://github.com/connortechnology)) - Fix package dependency on debian jessie [\#596](https://github.com/ZoneMinder/ZoneMinder/pull/596) ([manupap1](https://github.com/manupap1)) - updated local\_zoneminder type extension file [\#594](https://github.com/ZoneMinder/ZoneMinder/pull/594) ([ndobbs](https://github.com/ndobbs)) - Creating options documentation fixes \#568 [\#591](https://github.com/ZoneMinder/ZoneMinder/pull/591) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Convert french lang file to UTF-8 [\#589](https://github.com/ZoneMinder/ZoneMinder/pull/589) ([manupap1](https://github.com/manupap1)) - Digest auth [\#588](https://github.com/ZoneMinder/ZoneMinder/pull/588) ([connortechnology](https://github.com/connortechnology)) - Zmupdatefixes [\#584](https://github.com/ZoneMinder/ZoneMinder/pull/584) ([connortechnology](https://github.com/connortechnology)) - Update et\_ee.php [\#582](https://github.com/ZoneMinder/ZoneMinder/pull/582) ([hanzese](https://github.com/hanzese)) - Add zmeditconfigdata.sh script to source [\#577](https://github.com/ZoneMinder/ZoneMinder/pull/577) ([knnniggett](https://github.com/knnniggett)) - Close logger and database on exit [\#575](https://github.com/ZoneMinder/ZoneMinder/pull/575) ([manupap1](https://github.com/manupap1)) - Fix memory leaks with rtsp and a bug [\#574](https://github.com/ZoneMinder/ZoneMinder/pull/574) ([manupap1](https://github.com/manupap1)) - Fix a bug when closing RTSP session over TCP [\#573](https://github.com/ZoneMinder/ZoneMinder/pull/573) ([manupap1](https://github.com/manupap1)) - remove the case for level \>= 2. Since level is a bool, this code can ne... [\#572](https://github.com/ZoneMinder/ZoneMinder/pull/572) ([connortechnology](https://github.com/connortechnology)) - Add Control 3S N5071 Dome Ptz Camera [\#570](https://github.com/ZoneMinder/ZoneMinder/pull/570) ([jmcastro2014](https://github.com/jmcastro2014)) - Add the ability to specify the zm configdir at build time. [\#567](https://github.com/ZoneMinder/ZoneMinder/pull/567) ([knnniggett](https://github.com/knnniggett)) - Debian package migration to CMake and some improves with lintian help [\#565](https://github.com/ZoneMinder/ZoneMinder/pull/565) ([cosmedd](https://github.com/cosmedd)) - Use gnutls-openssl instead of gnutls to fix build with CMake. [\#564](https://github.com/ZoneMinder/ZoneMinder/pull/564) ([cosmedd](https://github.com/cosmedd)) - Use our own SSRC when sending packets on the RTP control stream [\#561](https://github.com/ZoneMinder/ZoneMinder/pull/561) ([manupap1](https://github.com/manupap1)) - Send keepalive messages if the rtsp server supports this feature [\#560](https://github.com/ZoneMinder/ZoneMinder/pull/560) ([manupap1](https://github.com/manupap1)) - Fixed bug in rtsp streaming caused by a bad string concatenation [\#557](https://github.com/ZoneMinder/ZoneMinder/pull/557) ([manupap1](https://github.com/manupap1)) - Add a stringVector join function for future use [\#556](https://github.com/ZoneMinder/ZoneMinder/pull/556) ([connortechnology](https://github.com/connortechnology)) - Fixed bug in rtsp streaming caused by a signed - unsigned conversion. [\#555](https://github.com/ZoneMinder/ZoneMinder/pull/555) ([manupap1](https://github.com/manupap1)) - Update Ubuntu install instructions [\#550](https://github.com/ZoneMinder/ZoneMinder/pull/550) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Ignore more files and initial travis framework [\#544](https://github.com/ZoneMinder/ZoneMinder/pull/544) ([kylejohnson](https://github.com/kylejohnson)) - Update Travis to ffmpeg 2.4.2 [\#539](https://github.com/ZoneMinder/ZoneMinder/pull/539) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Add libvlc to Travis [\#535](https://github.com/ZoneMinder/ZoneMinder/pull/535) ([knnniggett](https://github.com/knnniggett)) - 351-Rebase Attempt for ffmpeg stability fixes [\#531](https://github.com/ZoneMinder/ZoneMinder/pull/531) ([SteveGilvarry](https://github.com/SteveGilvarry)) - 478 Basic ONVIF Support [\#479](https://github.com/ZoneMinder/ZoneMinder/pull/479) ([altaroca](https://github.com/altaroca)) ## [v1.28.0](https://github.com/ZoneMinder/ZoneMinder/tree/v1.28.0) (2014-10-18) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.27.0...v1.28.0) **Merged pull requests:** - fixes ftbs with no ffmpeg support [\#530](https://github.com/ZoneMinder/ZoneMinder/pull/530) ([knnniggett](https://github.com/knnniggett)) - 498-Docker-Container-Broken [\#527](https://github.com/ZoneMinder/ZoneMinder/pull/527) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Changes to handling of tmpdir & related variables [\#524](https://github.com/ZoneMinder/ZoneMinder/pull/524) ([knnniggett](https://github.com/knnniggett)) - Fixes 520 travis build ffmpeg failure [\#521](https://github.com/ZoneMinder/ZoneMinder/pull/521) ([SteveGilvarry](https://github.com/SteveGilvarry)) - full systemd support [\#502](https://github.com/ZoneMinder/ZoneMinder/pull/502) ([knnniggett](https://github.com/knnniggett)) - Minor corrections to README.OpenSuse [\#501](https://github.com/ZoneMinder/ZoneMinder/pull/501) ([PX03AFK](https://github.com/PX03AFK)) - Allow use other webservers than apache. [\#493](https://github.com/ZoneMinder/ZoneMinder/pull/493) ([cosmedd](https://github.com/cosmedd)) - Initial attempt to migrate wiki to readthedocs \#434 [\#492](https://github.com/ZoneMinder/ZoneMinder/pull/492) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Update FI9821W\_Y2k.pm [\#485](https://github.com/ZoneMinder/ZoneMinder/pull/485) ([florian-asche](https://github.com/florian-asche)) - V4l to monitor [\#480](https://github.com/ZoneMinder/ZoneMinder/pull/480) ([connortechnology](https://github.com/connortechnology)) - Modified zmlinkcontent to chown and chmod content folder. Fixes \#463 [\#465](https://github.com/ZoneMinder/ZoneMinder/pull/465) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Fix for shared data size conflict [\#462](https://github.com/ZoneMinder/ZoneMinder/pull/462) ([knnniggett](https://github.com/knnniggett)) - Update rules [\#459](https://github.com/ZoneMinder/ZoneMinder/pull/459) ([whopperg](https://github.com/whopperg)) - Determine full path to arp [\#458](https://github.com/ZoneMinder/ZoneMinder/pull/458) ([knnniggett](https://github.com/knnniggett)) - Fixes errors when opening Filters \(issue \#34\) [\#457](https://github.com/ZoneMinder/ZoneMinder/pull/457) ([knnniggett](https://github.com/knnniggett)) - Fixed missing $ on ARRAY\(event\[id\]\). Fixes \#455 [\#456](https://github.com/ZoneMinder/ZoneMinder/pull/456) ([SteveGilvarry](https://github.com/SteveGilvarry)) - Wrap sort order. Fixes \#450 [\#451](https://github.com/ZoneMinder/ZoneMinder/pull/451) ([SteveGilvarry](https://github.com/SteveGilvarry)) - scripts: BusyBox compatibility [\#445](https://github.com/ZoneMinder/ZoneMinder/pull/445) ([clandmeter](https://github.com/clandmeter)) - Fixed issue DateTime handling in filter queries that broke timeline view... [\#442](https://github.com/ZoneMinder/ZoneMinder/pull/442) ([Tim-Craig](https://github.com/Tim-Craig)) - Cleaning up the Contribution section of the README [\#440](https://github.com/ZoneMinder/ZoneMinder/pull/440) ([kylejohnson](https://github.com/kylejohnson)) - Add Bountysource badge to README [\#438](https://github.com/ZoneMinder/ZoneMinder/pull/438) ([bountysource-support](https://github.com/bountysource-support)) - Add new colums to zm\_create.sql.in [\#426](https://github.com/ZoneMinder/ZoneMinder/pull/426) ([m-bene](https://github.com/m-bene)) - Ffmpegoptions [\#421](https://github.com/ZoneMinder/ZoneMinder/pull/421) ([m-bene](https://github.com/m-bene)) - Update zm\_jpeg.cpp [\#418](https://github.com/ZoneMinder/ZoneMinder/pull/418) ([ghost](https://github.com/ghost)) - Added an FAQ for AlarmCheckMethod [\#416](https://github.com/ZoneMinder/ZoneMinder/pull/416) ([kylejohnson](https://github.com/kylejohnson)) - make skin selection persistent [\#415](https://github.com/ZoneMinder/ZoneMinder/pull/415) ([m-bene](https://github.com/m-bene)) - Added a doc for contributing to the project [\#413](https://github.com/ZoneMinder/ZoneMinder/pull/413) ([kylejohnson](https://github.com/kylejohnson)) - Update zmtrigger.pl.in [\#411](https://github.com/ZoneMinder/ZoneMinder/pull/411) ([martin67](https://github.com/martin67)) - Add the web/api folder to cmake [\#409](https://github.com/ZoneMinder/ZoneMinder/pull/409) ([mastertheknife](https://github.com/mastertheknife)) - Move API to under web dir [\#408](https://github.com/ZoneMinder/ZoneMinder/pull/408) ([kylejohnson](https://github.com/kylejohnson)) - Rtsp digest [\#407](https://github.com/ZoneMinder/ZoneMinder/pull/407) ([m-bene](https://github.com/m-bene)) - focus popup windows [\#406](https://github.com/ZoneMinder/ZoneMinder/pull/406) ([m-bene](https://github.com/m-bene)) - remove call of undefined "fixDevices" function [\#405](https://github.com/ZoneMinder/ZoneMinder/pull/405) ([m-bene](https://github.com/m-bene)) - Zms/videostream improvements [\#404](https://github.com/ZoneMinder/ZoneMinder/pull/404) ([Sune1337](https://github.com/Sune1337)) - RESTful API in CakePHP, and docs [\#403](https://github.com/ZoneMinder/ZoneMinder/pull/403) ([kylejohnson](https://github.com/kylejohnson)) - Remove SVN install from Travis CI [\#395](https://github.com/ZoneMinder/ZoneMinder/pull/395) ([hamiltont](https://github.com/hamiltont)) - Remove Subversion from prerequesite lists [\#393](https://github.com/ZoneMinder/ZoneMinder/pull/393) ([hamiltont](https://github.com/hamiltont)) - Dockerfile works properly, documentation updated [\#392](https://github.com/ZoneMinder/ZoneMinder/pull/392) ([hamiltont](https://github.com/hamiltont)) - Remove apt-get upgrade [\#390](https://github.com/ZoneMinder/ZoneMinder/pull/390) ([hamiltont](https://github.com/hamiltont)) - Update README.OpenSuse [\#389](https://github.com/ZoneMinder/ZoneMinder/pull/389) ([PX03AFK](https://github.com/PX03AFK)) - Update CMakeLists.txt [\#388](https://github.com/ZoneMinder/ZoneMinder/pull/388) ([PX03AFK](https://github.com/PX03AFK)) - Update zoneminder.cmake.OS13.spec - minor corrections [\#387](https://github.com/ZoneMinder/ZoneMinder/pull/387) ([PX03AFK](https://github.com/PX03AFK)) - fix sql error which prevents remote login from working [\#385](https://github.com/ZoneMinder/ZoneMinder/pull/385) ([maciekczwa](https://github.com/maciekczwa)) - Fix window sizes and input field sizes for flat skin [\#381](https://github.com/ZoneMinder/ZoneMinder/pull/381) ([m-bene](https://github.com/m-bene)) - Fix reload loop on switching skins [\#380](https://github.com/ZoneMinder/ZoneMinder/pull/380) ([m-bene](https://github.com/m-bene)) - Ability to skip frames in motion detection. [\#377](https://github.com/ZoneMinder/ZoneMinder/pull/377) ([Sune1337](https://github.com/Sune1337)) - same dvr controls in event as in monitor [\#375](https://github.com/ZoneMinder/ZoneMinder/pull/375) ([m-bene](https://github.com/m-bene)) - do not quote column names in parse filter [\#374](https://github.com/ZoneMinder/ZoneMinder/pull/374) ([m-bene](https://github.com/m-bene)) - Fix 'Undefined index: filter' php warnings for filter view when [\#373](https://github.com/ZoneMinder/ZoneMinder/pull/373) ([m-bene](https://github.com/m-bene)) - Reduce window size to exclude task bar area [\#371](https://github.com/ZoneMinder/ZoneMinder/pull/371) ([m-bene](https://github.com/m-bene)) - Update zoneminder.tmpfiles [\#367](https://github.com/ZoneMinder/ZoneMinder/pull/367) ([PX03AFK](https://github.com/PX03AFK)) - Update zoneminder.cmake.OS13.spec [\#362](https://github.com/ZoneMinder/ZoneMinder/pull/362) ([PX03AFK](https://github.com/PX03AFK)) - Update README.OpenSuse [\#361](https://github.com/ZoneMinder/ZoneMinder/pull/361) ([PX03AFK](https://github.com/PX03AFK)) - fix eyezm authentication issue [\#359](https://github.com/ZoneMinder/ZoneMinder/pull/359) ([knnniggett](https://github.com/knnniggett)) - Fix prev button while in gapless mode. All buttons tested and working. [\#358](https://github.com/ZoneMinder/ZoneMinder/pull/358) ([knnniggett](https://github.com/knnniggett)) - Update zmupdate.pl.in [\#353](https://github.com/ZoneMinder/ZoneMinder/pull/353) ([barjac](https://github.com/barjac)) - make curl header check case insensitive [\#352](https://github.com/ZoneMinder/ZoneMinder/pull/352) ([m-bene](https://github.com/m-bene)) - tie distro/opensuse folder into cmake build process [\#349](https://github.com/ZoneMinder/ZoneMinder/pull/349) ([knnniggett](https://github.com/knnniggett)) - Initial upload for opensuse rpm [\#348](https://github.com/ZoneMinder/ZoneMinder/pull/348) ([PX03AFK](https://github.com/PX03AFK)) - Patch for Debian bug 736516 - FTBFS on powerpc arch. [\#346](https://github.com/ZoneMinder/ZoneMinder/pull/346) ([knnniggett](https://github.com/knnniggett)) - Nagyrobi [\#342](https://github.com/ZoneMinder/ZoneMinder/pull/342) ([knnniggett](https://github.com/knnniggett)) - add feature to extend preclusive zone alarm state by x frames [\#338](https://github.com/ZoneMinder/ZoneMinder/pull/338) ([m-bene](https://github.com/m-bene)) - Support building with new libavcodec versions. [\#325](https://github.com/ZoneMinder/ZoneMinder/pull/325) ([elenril](https://github.com/elenril)) - Mysql2 pdo [\#231](https://github.com/ZoneMinder/ZoneMinder/pull/231) ([connortechnology](https://github.com/connortechnology)) ## [v1.27.0](https://github.com/ZoneMinder/ZoneMinder/tree/v1.27.0) (2014-03-15) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26.5...v1.27.0) **Merged pull requests:** - zmcamtool.pl - import and export ptz camera controls & camera presets [\#318](https://github.com/ZoneMinder/ZoneMinder/pull/318) ([kylejohnson](https://github.com/kylejohnson)) - Example script to react to monitor alarms [\#317](https://github.com/ZoneMinder/ZoneMinder/pull/317) ([kylejohnson](https://github.com/kylejohnson)) - Change comments for many Camera subclasses [\#316](https://github.com/ZoneMinder/ZoneMinder/pull/316) ([nereocystis](https://github.com/nereocystis)) - Enable universe in the Docker container \[ci skip\] [\#310](https://github.com/ZoneMinder/ZoneMinder/pull/310) ([kylejohnson](https://github.com/kylejohnson)) - Add wget to Dockerfile prereq [\#309](https://github.com/ZoneMinder/ZoneMinder/pull/309) ([kylejohnson](https://github.com/kylejohnson)) - Add paths to flat skin in configure.ac and Makefile.am [\#308](https://github.com/ZoneMinder/ZoneMinder/pull/308) ([kylejohnson](https://github.com/kylejohnson)) - Zoneminder - flat theme. [\#303](https://github.com/ZoneMinder/ZoneMinder/pull/303) ([justinlawrence](https://github.com/justinlawrence)) - Additional compile guards to allow compilation on non-x86 archs [\#302](https://github.com/ZoneMinder/ZoneMinder/pull/302) ([pjhacnau](https://github.com/pjhacnau)) - Adding PTZ module for Toshiba IK-WB11A [\#300](https://github.com/ZoneMinder/ZoneMinder/pull/300) ([Tim-Craig](https://github.com/Tim-Craig)) - Add cURL source type [\#297](https://github.com/ZoneMinder/ZoneMinder/pull/297) ([mastertheknife](https://github.com/mastertheknife)) - glob ptz scripts under control folder [\#294](https://github.com/ZoneMinder/ZoneMinder/pull/294) ([knnniggett](https://github.com/knnniggett)) - Enclose value of the Notes attribute with quotes. [\#293](https://github.com/ZoneMinder/ZoneMinder/pull/293) ([hankintosh](https://github.com/hankintosh)) - Fix for few problems in Debian packaging [\#290](https://github.com/ZoneMinder/ZoneMinder/pull/290) ([dmak](https://github.com/dmak)) - Foscam IP cameras control script [\#289](https://github.com/ZoneMinder/ZoneMinder/pull/289) ([dmak](https://github.com/dmak)) - Specfile changes related to zmfix [\#284](https://github.com/ZoneMinder/ZoneMinder/pull/284) ([knnniggett](https://github.com/knnniggett)) - Remove references to zmfix in /distros [\#283](https://github.com/ZoneMinder/ZoneMinder/pull/283) ([nkwood](https://github.com/nkwood)) - Add zmMontageScale so montage scale is also 'saved' [\#282](https://github.com/ZoneMinder/ZoneMinder/pull/282) ([scottgrobinson](https://github.com/scottgrobinson)) - In the web/includes/functions.php there were some html formatting errors... [\#281](https://github.com/ZoneMinder/ZoneMinder/pull/281) ([martonmiklos](https://github.com/martonmiklos)) - Remove zmfix [\#280](https://github.com/ZoneMinder/ZoneMinder/pull/280) ([mastertheknife](https://github.com/mastertheknife)) - Add libVLC monitor type [\#277](https://github.com/ZoneMinder/ZoneMinder/pull/277) ([ebarnard](https://github.com/ebarnard)) - Disables non-POSIX warning when using gnu wildcard [\#276](https://github.com/ZoneMinder/ZoneMinder/pull/276) ([knnniggett](https://github.com/knnniggett)) - remove embedded jquery. \(fixes \#274\) [\#275](https://github.com/ZoneMinder/ZoneMinder/pull/275) ([kylejohnson](https://github.com/kylejohnson)) - Fixes \#313, initial commit of 'working' dockerfile [\#314](https://github.com/ZoneMinder/ZoneMinder/pull/314) ([kylejohnson](https://github.com/kylejohnson)) ## [v1.26.5](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26.5) (2013-12-16) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26.4...v1.26.5) **Merged pull requests:** - Add reference to zm\_update-1.26.5.sql in Makefile.am [\#269](https://github.com/ZoneMinder/ZoneMinder/pull/269) ([knnniggett](https://github.com/knnniggett)) - Detection Support for WansView Cams [\#268](https://github.com/ZoneMinder/ZoneMinder/pull/268) ([Phhere](https://github.com/Phhere)) - use proper DBI parameter passing to improve security [\#264](https://github.com/ZoneMinder/ZoneMinder/pull/264) ([connortechnology](https://github.com/connortechnology)) - Fix RTSP decoding errors in 1.26.4 \(addresses \#221\) [\#259](https://github.com/ZoneMinder/ZoneMinder/pull/259) ([ebarnard](https://github.com/ebarnard)) - Network Detection Support for Wansview [\#257](https://github.com/ZoneMinder/ZoneMinder/pull/257) ([Phhere](https://github.com/Phhere)) - Fix checkJsonError messages [\#256](https://github.com/ZoneMinder/ZoneMinder/pull/256) ([Phhere](https://github.com/Phhere)) - Update README.md [\#255](https://github.com/ZoneMinder/ZoneMinder/pull/255) ([zdanek](https://github.com/zdanek)) - ipv6 support [\#252](https://github.com/ZoneMinder/ZoneMinder/pull/252) ([guotie](https://github.com/guotie)) - Better inno d bupdate [\#251](https://github.com/ZoneMinder/ZoneMinder/pull/251) ([connortechnology](https://github.com/connortechnology)) - Fix shared memory errors on centos 6.4 [\#250](https://github.com/ZoneMinder/ZoneMinder/pull/250) ([insidenothing](https://github.com/insidenothing)) - Update zoneminder.service [\#246](https://github.com/ZoneMinder/ZoneMinder/pull/246) ([dtmf](https://github.com/dtmf)) - remove extra stuff that I don't think we need because we are the source. Opinions? [\#240](https://github.com/ZoneMinder/ZoneMinder/pull/240) ([connortechnology](https://github.com/connortechnology)) - Cast content\_length to signed int for error-check comparison [\#232](https://github.com/ZoneMinder/ZoneMinder/pull/232) ([josephevans](https://github.com/josephevans)) - Apply INSERTs in Event::AddFrames in batches to fix issue \#222 [\#223](https://github.com/ZoneMinder/ZoneMinder/pull/223) ([fastolfe](https://github.com/fastolfe)) - ffmpeg detection improvements [\#218](https://github.com/ZoneMinder/ZoneMinder/pull/218) ([mastertheknife](https://github.com/mastertheknife)) - ZoneMinder Dutch Translation updates by Alco \(a.k. nightcrawler\) [\#211](https://github.com/ZoneMinder/ZoneMinder/pull/211) ([kylejohnson](https://github.com/kylejohnson)) - Change Prev Button functionality [\#207](https://github.com/ZoneMinder/ZoneMinder/pull/207) ([knnniggett](https://github.com/knnniggett)) - Delete PATH\_BUILD and TIME\_BUILD from zm.conf and fix ZM\_DB\_TYPE [\#243](https://github.com/ZoneMinder/ZoneMinder/pull/243) ([mastertheknife](https://github.com/mastertheknife)) - Removeversionnumberfromzm.conf [\#242](https://github.com/ZoneMinder/ZoneMinder/pull/242) ([connortechnology](https://github.com/connortechnology)) - Add alarm reference image blend percentage option and replace the text field if fast blends are enabled [\#241](https://github.com/ZoneMinder/ZoneMinder/pull/241) ([mastertheknife](https://github.com/mastertheknife)) - Fix marker-out-of-bounds crash when defining zone points [\#233](https://github.com/ZoneMinder/ZoneMinder/pull/233) ([fastolfe](https://github.com/fastolfe)) ## [v1.26.4](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26.4) (2013-10-08) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26.3...v1.26.4) **Merged pull requests:** - Change frameserver warnings to debug level 2 [\#205](https://github.com/ZoneMinder/ZoneMinder/pull/205) ([knnniggett](https://github.com/knnniggett)) - Create pkgdatadir in make [\#203](https://github.com/ZoneMinder/ZoneMinder/pull/203) ([knnniggett](https://github.com/knnniggett)) - Signal improvements and fixes [\#201](https://github.com/ZoneMinder/ZoneMinder/pull/201) ([mastertheknife](https://github.com/mastertheknife)) - Create ZM\_PATH\_DATA and point zmupdate to ZM\_PATH\_DATA/db [\#200](https://github.com/ZoneMinder/ZoneMinder/pull/200) ([knnniggett](https://github.com/knnniggett)) - remove ${CMAKE\_CURRENT\_SOURCE\_DIR} from add\_custom\_target [\#199](https://github.com/ZoneMinder/ZoneMinder/pull/199) ([knnniggett](https://github.com/knnniggett)) - Added missing word in readme [\#194](https://github.com/ZoneMinder/ZoneMinder/pull/194) ([WDKevin](https://github.com/WDKevin)) - Add cmake to ZoneMinder [\#178](https://github.com/ZoneMinder/ZoneMinder/pull/178) ([mastertheknife](https://github.com/mastertheknife)) - Rtsp updates [\#174](https://github.com/ZoneMinder/ZoneMinder/pull/174) ([POKKAHOH](https://github.com/POKKAHOH)) - Solution for Issue \#170 [\#172](https://github.com/ZoneMinder/ZoneMinder/pull/172) ([raulcaj](https://github.com/raulcaj)) - Fixing debian build files including automated database setup [\#164](https://github.com/ZoneMinder/ZoneMinder/pull/164) ([jaydio](https://github.com/jaydio)) - Add fedora rpm development files to zoneminder source tree [\#163](https://github.com/ZoneMinder/ZoneMinder/pull/163) ([knnniggett](https://github.com/knnniggett)) - Improve Chrome browser support & log streaming events [\#162](https://github.com/ZoneMinder/ZoneMinder/pull/162) ([knnniggett](https://github.com/knnniggett)) - Can't seem to catch a break tonight. Moving debian files into correct folder [\#149](https://github.com/ZoneMinder/ZoneMinder/pull/149) ([knnniggett](https://github.com/knnniggett)) - Move debian folder under distros [\#148](https://github.com/ZoneMinder/ZoneMinder/pull/148) ([knnniggett](https://github.com/knnniggett)) - Removing the redhat folder from the root for real this time [\#141](https://github.com/ZoneMinder/ZoneMinder/pull/141) ([knnniggett](https://github.com/knnniggett)) - Redhat [\#136](https://github.com/ZoneMinder/ZoneMinder/pull/136) ([knnniggett](https://github.com/knnniggett)) - Error correction in database creation script [\#122](https://github.com/ZoneMinder/ZoneMinder/pull/122) ([dukess](https://github.com/dukess)) - Rewritten the query to allow mysql to use indexes [\#121](https://github.com/ZoneMinder/ZoneMinder/pull/121) ([rkojedzinszky](https://github.com/rkojedzinszky)) - Update zmupdate.pl.in for 1.26.3 release [\#119](https://github.com/ZoneMinder/ZoneMinder/pull/119) ([knnniggett](https://github.com/knnniggett)) ## [v1.26.3](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26.3) (2013-09-10) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26.2...v1.26.3) **Merged pull requests:** - Add 1.26.1 and 1.26.2 releases to zmupdate [\#116](https://github.com/ZoneMinder/ZoneMinder/pull/116) ([knnniggett](https://github.com/knnniggett)) ## [v1.26.2](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26.2) (2013-09-06) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26.1...v1.26.2) **Merged pull requests:** - Use GitHub repo for version check [\#111](https://github.com/ZoneMinder/ZoneMinder/pull/111) ([chriswiggins](https://github.com/chriswiggins)) ## [v1.26.1](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26.1) (2013-09-06) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26.0...v1.26.1) ## [v1.26.0](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26.0) (2013-09-05) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26-beta.3...v1.26.0) ## [v1.26-beta.3](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26-beta.3) (2013-08-28) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26-beta.2...v1.26-beta.3) ## [v1.26-beta.2](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26-beta.2) (2013-08-15) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.26-beta.1...v1.26-beta.2) ## [v1.26-beta.1](https://github.com/ZoneMinder/ZoneMinder/tree/v1.26-beta.1) (2013-08-13) [Full Changelog](https://github.com/ZoneMinder/ZoneMinder/compare/v1.25...v1.26-beta.1) ## [v1.25](https://github.com/ZoneMinder/ZoneMinder/tree/v1.25) (2013-04-12) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*ZoneMinder-1.32.2/TODO0000644000000000000000000000003413365153155013043 0ustar rootrootPlease see README.md file. ZoneMinder-1.32.2/web/0000755000000000000000000000000013365153156013134 5ustar rootrootZoneMinder-1.32.2/web/.travis.yml0000644000000000000000000000647613365153155015261 0ustar rootrootlanguage: php php: - 5.2 - 5.3 - 5.4 env: - DB=mysql - DB=pgsql - DB=sqlite matrix: include: - php: 5.4 env: - PHPCS=1 before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi" - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test2;'; fi" - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test3;'; fi" - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi" - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test2;' -U postgres -d cakephp_test; fi" - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test3;' -U postgres -d cakephp_test; fi" - chmod -R 777 ./app/tmp - sudo apt-get install lighttpd - pear channel-discover pear.cakephp.org - pear install --alldeps cakephp/CakePHP_CodeSniffer - phpenv rehash - set +H - echo " array( 'datasource' => 'Database/Mysql', 'host' => '0.0.0.0', 'login' => 'travis' ), 'pgsql' => array( 'datasource' => 'Database/Postgres', 'host' => '127.0.0.1', 'login' => 'postgres', 'database' => 'cakephp_test', 'schema' => array( 'default' => 'public', 'test' => 'public', 'test2' => 'test2', 'test_database_three' => 'test3' ) ), 'sqlite' => array( 'datasource' => 'Database/Sqlite', 'database' => array( 'default' => ':memory:', 'test' => ':memory:', 'test2' => '/tmp/cakephp_test2.db', 'test_database_three' => '/tmp/cakephp_test3.db' ), ) ); public \$default = array( 'persistent' => false, 'host' => '', 'login' => '', 'password' => '', 'database' => 'cakephp_test', 'prefix' => '' ); public \$test = array( 'persistent' => false, 'host' => '', 'login' => '', 'password' => '', 'database' => 'cakephp_test', 'prefix' => '' ); public \$test2 = array( 'persistent' => false, 'host' => '', 'login' => '', 'password' => '', 'database' => 'cakephp_test2', 'prefix' => '' ); public \$test_database_three = array( 'persistent' => false, 'host' => '', 'login' => '', 'password' => '', 'database' => 'cakephp_test3', 'prefix' => '' ); public function __construct() { \$db = 'mysql'; if (!empty(\$_SERVER['DB'])) { \$db = \$_SERVER['DB']; } foreach (array('default', 'test', 'test2', 'test_database_three') as \$source) { \$config = array_merge(\$this->{\$source}, \$this->identities[\$db]); if (is_array(\$config['database'])) { \$config['database'] = \$config['database'][\$source]; } if (!empty(\$config['schema']) && is_array(\$config['schema'])) { \$config['schema'] = \$config['schema'][\$source]; } \$this->{\$source} = \$config; } } }" > app/Config/database.php script: - sh -c "if [ '$PHPCS' != '1' ]; then ./lib/Cake/Console/cake test core AllTests --stderr; else phpcs -p --extensions=php --standard=CakePHP ./lib/Cake; fi" notifications: email: falseZoneMinder-1.32.2/web/views/0000755000000000000000000000000013365153156014271 5ustar rootrootZoneMinder-1.32.2/web/views/image.php0000644000000000000000000002747513365153156016103 0ustar rootroot$_REQUEST['eid'])); if ( !$Event ) { header('HTTP/1.0 404 Not Found'); Fatal('Event '.$_REQUEST['eid'].' Not found'); return; } # if alarm, get the fid of the first alarmed frame if available and let the # fid= code continue processing it. Sort it to get the first alarmed frame if ( $_REQUEST['fid'] == 'alarm' ) { $Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'Type'=>'Alarm'), array('order'=>'FrameId ASC')); if ( !$Frame ) # no alarms $Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'])); # first frame if ( !$Frame ) { Warning("No frame found for event " + $_REQUEST['eid']); $Frame = new Frame(); $Frame->Delta(1); $Frame->FrameId('snapshot'); } $_REQUEST['fid']=$Frame->FrameId(); } if ( $_REQUEST['fid'] == 'snapshot' ) { $Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'Score'=>$Event->MaxScore())); if ( !$Frame ) $Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'])); if ( !$Frame ) { Warning("No frame found for event " + $_REQUEST['eid']); $Frame = new Frame(); $Frame->Delta(1); $Frame->FrameId('snapshot'); } $Monitor = $Event->Monitor(); if ( $Monitor->SaveJPEGs() & 1 ) { # If we store Frames as jpgs, then we don't store a snapshot $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d',$Frame->FrameId()).'-'.$show.'.jpg'; } else { $path = $Event->Path().'/snapshot.jpg'; } } else { $Frame = Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'FrameId'=>$_REQUEST['fid'])); if ( ! $Frame ) { $previousBulkFrame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND FrameId < ? ORDER BY FrameID DESC LIMIT 1', NULL, array($_REQUEST['eid'], $_REQUEST['fid']) ); $nextBulkFrame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND FrameId > ? ORDER BY FrameID ASC LIMIT 1', NULL, array($_REQUEST['eid'], $_REQUEST['fid']) ); if ( $previousBulkFrame and $nextBulkFrame ) { $Frame = new Frame($previousBulkFrame); $Frame->FrameId($_REQUEST['fid']); $percentage = ($Frame->FrameId() - $previousBulkFrame['FrameId']) / ($nextBulkFrame['FrameId'] - $previousBulkFrame['FrameId']); $Frame->Delta($previousBulkFrame['Delta'] + floor( 100* ( $nextBulkFrame['Delta'] - $previousBulkFrame['Delta'] ) * $percentage )/100); Logger::Debug("Got virtual frame from Bulk Frames previous delta: " . $previousBulkFrame['Delta'] . " + nextdelta:" . $nextBulkFrame['Delta'] . ' - ' . $previousBulkFrame['Delta'] . ' * ' . $percentage ); } else { Fatal('No Frame found for event('.$_REQUEST['eid'].') and frame id('.$_REQUEST['fid'].')'); } } // Frame can be non-existent. We have Bulk frames. So now we should try to load the bulk frame $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d',$Frame->FrameId()).'-'.$show.'.jpg'; Logger::Debug("Path: $path"); } } else { # If we are only specifying fid, then the fid must be the primary key into the frames table. But when the event is specified, then it is the frame # $Frame = Frame::find_one(array('Id'=>$_REQUEST['fid'])); if ( !$Frame ) { header('HTTP/1.0 404 Not Found'); Fatal('Frame ' . $_REQUEST['fid'] . ' Not Found'); return; } $Event = Event::find_one(array('Id'=>$Frame->EventId())); if ( !$Event ) { header('HTTP/1.0 404 Not Found'); Fatal('Event ' . $Frame->EventId() . ' Not Found'); return; } $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d',$Frame->FrameId()).'-'.$show.'.jpg'; } # end if have eid if ( !file_exists($path) ) { Logger::Debug("$path does not exist"); # Generate the frame JPG if ( ($show == 'capture') and $Event->DefaultVideo() ) { if ( !file_exists($Event->Path().'/'.$Event->DefaultVideo()) ) { header('HTTP/1.0 404 Not Found'); Fatal("Can't create frame images from video because there is no video file for this event at (".$Event->Path().'/'.$Event->DefaultVideo() ); } $command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -frames:v 1 '.$path; #$command ='ffmpeg -ss '. $Frame->Delta() .' -i '.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path; #$command ='ffmpeg -v 0 -i '.$Storage->Path().'/'.$Event->Path().'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$Frame->FrameId().'),setpts=PTS-STARTPTS" '.$path; Logger::Debug("Running $command"); $output = array(); $retval = 0; exec( $command, $output, $retval ); Logger::Debug("Command: $command, retval: $retval, output: " . implode("\n", $output)); if ( ! file_exists( $path ) ) { header('HTTP/1.0 404 Not Found'); Fatal("Can't create frame images from video for this event (".$Event->DefaultVideo() ); } # Generating an image file will use up more disk space, so update the Event record. $Event->DiskSpace(null); $Event->save(); } else { header('HTTP/1.0 404 Not Found'); Fatal("Can't create frame $show images from video because there is no video file for this event at ". $Event->Path().'/'.$Event->DefaultVideo() ); } } # end if ! file_exists($path) } else { Warning('Loading images by path is deprecated'); $dir_events = realpath(ZM_DIR_EVENTS); $path = realpath($dir_events . '/' . $_REQUEST['path']); $pos = strpos($path, $dir_events); if ( $pos == 0 && $pos !== false ) { if ( ! empty($user['MonitorIds']) ) { $imageOk = false; $pathMonId = substr($path, 0, strspn($path, '1234567890')); foreach ( preg_split('/["\'\s]*,["\'\s]*/', $user['MonitorIds']) as $monId ) { if ( $pathMonId == $monId ) { $imageOk = true; break; } } if ( !$imageOk ) $errorText = 'No image permissions'; } } else { $errorText = 'Invalid image path'; } if ( !file_exists($path) ) { header('HTTP/1.0 404 Not Found'); Fatal("Image not found at $path"); } } $scale = 0; if ( !empty($_REQUEST['scale']) ) { if ( is_numeric($_REQUEST['scale']) ) { $x = $_REQUEST['scale']; if ( $x >= 1 and $x <= 400 ) $scale = $x; } } $width = 0; if ( !empty($_REQUEST['width']) ) { Logger::Debug("Setting width: " . $_REQUEST['width']); if ( is_numeric($_REQUEST['width']) ) { $x = $_REQUEST['width']; if ( $x >= 10 and $x <= 8000 ) $width = $x; } } $height = 0; if ( !empty($_REQUEST['height']) ) { if ( is_numeric($_REQUEST['height']) ) { $x = $_REQUEST['height']; if ( $x >= 10 and $x <= 8000 ) $height = $x; } } if ( $errorText ) { Error($errorText); } else { # Clears the output buffer. Not sure what is there, but have had troubles. ob_end_clean(); header('Content-type: image/jpeg'); if ( ( $scale==0 || $scale==100 ) && ($width==0) && ($height==0) ) { # This is so that Save Image As give a useful filename if ( $Event ) { $filename = $Event->MonitorId().'_'.$Event->Id().'_'.$Frame->FrameId().'.jpg'; header('Content-Disposition: inline; filename="' . $filename . '"'); } if ( !readfile($path) ) { Error('No bytes read from '. $path); } } else { Logger::Debug("Doing a scaled image: scale($scale) width($width) height($height)"); $i = 0; if ( ! ( $width && $height ) ) { $i = imagecreatefromjpeg($path); $oldWidth = imagesx($i); $oldHeight = imagesy($i); if ( $width == 0 && $height == 0 ) { // scale has to be set to get here with both zero $width = $oldWidth * $scale / 100.0; $height= $oldHeight * $scale / 100.0; } elseif ( $width == 0 && $height != 0 ) { $width = ($height * $oldWidth) / $oldHeight; } elseif ( $width != 0 && $height == 0 ) { $height = ($width * $oldHeight) / $oldWidth; Logger::Debug("Figuring out height using width: $height = ($width * $oldHeight) / $oldWidth"); } if ( $width == $oldWidth && $height == $oldHeight ) { Warning('No change to width despite scaling.'); } } # Slight optimisation, thumbnails always specify width and height, so we can cache them. $scaled_path = preg_replace('/\.jpg$/', "-${width}x${height}.jpg", $path); if ( $Event ) { $filename = $Event->MonitorId().'_'.$Event->Id().'_'.$Frame->FrameId()."-${width}x${height}.jpg"; header('Content-Disposition: inline; filename="' . $filename . '"'); } if ( !( file_exists($scaled_path) and readfile($scaled_path) ) ) { Logger::Debug("Cached scaled image does not exist at $scaled_path or is no good.. Creating it"); ob_start(); if ( !$i ) $i = imagecreatefromjpeg($path); $iScale = imagescale($i, $width, $height); imagejpeg($iScale); imagedestroy($i); imagedestroy($iScale); $scaled_jpeg_data = ob_get_contents(); file_put_contents($scaled_path, $scaled_jpeg_data); ob_end_clean(); echo $scaled_jpeg_data; } else { Logger::Debug("Sending $scaled_path"); $bytes = readfile($scaled_path); if ( !$bytes ) { Error('No bytes read from '. $scaled_path); } else { Logger::Debug("$bytes sent"); } } } } exit(); ZoneMinder-1.32.2/web/views/view_video.php0000644000000000000000000000620213365153156017142 0ustar rootrootPath().'/'.$Event->DefaultVideo(); Logger::Debug("Path: $path"); } else if ( ! empty($_REQUEST['event_id']) ) { $Event = new Event($_REQUEST['event_id']); $path = $Event->Path().'/'.$Event->DefaultVideo(); Logger::Debug("Path: $path"); } else { $errorText = 'No video path'; } if ( $errorText ) { Error($errorText); header('HTTP/1.0 404 Not Found'); die(); } $size = filesize($path); $fh = @fopen($path,'rb'); if ( ! $fh ) { header('HTTP/1.0 404 Not Found'); die(); } $begin = 0; $end = $size-1; $length = $size; if ( isset($_SERVER['HTTP_RANGE']) ) { Logger::Debug('Using Range ' . $_SERVER['HTTP_RANGE']); if ( preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches) ) { $begin = intval($matches[1]); if ( ! empty($matches[2]) ) { $end = intval($matches[2]); } $length = $end - $begin + 1; Logger::Debug("Using Range $begin $end size: $size, length: $length"); } } # end if HTTP_RANGE header('Content-type: video/mp4'); header('Accept-Ranges: bytes'); header('Content-Length: '.$length); # This is so that Save Image As give a useful filename if ( $Event ) { header('Content-Disposition: inline; filename="' . $Event->DefaultVideo() . '"'); } else { header('Content-Disposition: inline;'); } if ( $begin > 0 || $end < $size-1 ) { header('HTTP/1.0 206 Partial Content'); header("Content-Range: bytes $begin-$end/$size"); header("Content-Transfer-Encoding: binary\n"); header('Connection: close'); } else { header('HTTP/1.0 200 OK'); } // Apparently without these we get a few extra bytes of output at the end... ob_clean(); flush(); $cur = $begin; fseek($fh, $begin, 0); while( $length && ( !feof($fh) ) && ( connection_status() == 0 ) ) { $amount = min(1024*16, $length); print fread( $fh, $amount ); $length -= $amount; # Why introduce a speed limit? #usleep(100); flush(); } fclose($fh); exit(); ZoneMinder-1.32.2/web/views/archive.php0000644000000000000000000000400413365153156016421 0ustar rootroot ZoneMinder-1.32.2/web/scripts/0000755000000000000000000000000013365153156014623 5ustar rootrootZoneMinder-1.32.2/web/scripts/retag.sh0000644000000000000000000000222313365153156016260 0ustar rootroot#!/bin/sh # # ========================================================================== # # ZoneMinder PHP Retagging Script, $Date$, $Revision$ # Copyright (C) 2003, 2004, 2005, 2006 Philip Coombes # # 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. # # ========================================================================== # # Short shell script for changing short open tags to long ones. # # ========================================================================== # perl swap.pl '<\?=' '${tmpfile}" ) or die( "Can't open '$tmpfile' for writing" ); my $count = 0; while( ) { $count += s/$from/$to/g; print( TOFILE ); } close( FROMFILE ); close( TOFILE ); if ( $count ) { rename( $tmpfile, $file ) or die( "Can't rename '$tmpfile' to '$file'" ); } else { unlink( $tmpfile ) or die( "Can'delete rename '$tmpfile' to '$file'" ); } if ( $count ) { print( "Processed $file" ); print( ": $count changes" ); print( "\n" ); } } ZoneMinder-1.32.2/web/robots.txt0000644000000000000000000000003513365153156015203 0ustar rootrootUser-agent: * Disallow: / ###ZoneMinder-1.32.2/web/fonts/0000755000000000000000000000000013365153155014264 5ustar rootrootZoneMinder-1.32.2/web/fonts/glyphicons-halflings-regular.eot0000644000000000000000000004723713365153155022575 0ustar rootrootŸNAMLP',(GLYPHICONS HalflingsRegularxVersion 1.009;PS 001.009;hotconv 1.0.70;makeotf.lib2.5.583298GLYPHICONS Halflings RegularBSGPฉMMF๎อ้Œฯาูฃ(uสŒ<0DใB/X ๏N๏ ˆCC๊^ว rmR2skษหPJ"5+–gl้W*iีW–/E๎‘œำ4#ฌิฃUฆ~ฒf‰‘UDฤน๗ˆซฑเJท1แ/!/žบสsช7’“kŠ•”(บˆกhN๘้8oํd$yqŽน1ณโึ9ƒ@-‰‚HG’๔ตS"๘Fj๔ุ 6C3”ค&ž‡ม๘ชW51มำืBŸฏaห๊QaR†U/๕ถ{*ฟ‚ห๏‚=–@d๔๘h$ก1ษT—nc+c’Aกงผ •Zษ€ค@Qัcญa‡ีl๗2>สKฐศm๓' “หC‡HMฤฌfB‰X,พY๒ยpจeข ๎ธU๘ุ*า”z m‚หหiO1nEฦ.›„ไ hx!aC XTฺVขลฉย‹– —้Rฅš%ฅ|Iไ H๐ลีPƒ5"ลb’Nฒต=โ๘ƒrู/_ๅRŒ›”™_ เ%า„uzษ้า˜ึ5’2ฤกาใPฺ)žิžฦรFƒ7S‹q„Fภ{nแiaทธ@Dะsˆ;š}9โฌฅ?ลบ‘งย R{ฆTkํ;ตวœืU\NžZ๘›Q-ป^ิs7๒f 0˜สฦS3A _n`W7Pp˜ป๔เiซํณ!๐gุ/เ_pปมาZ€-=รืฅ~WZ#/แ4 KF`ด ปŒz฿า0| D‚ัต์‚&dรคI‰ดŽรม;ทM์”{'ถom†”mข I !wi9|H:งงภป็๗สพ{๛~๖นqบธฉO๘ๅ๔๎ฉ ๚›,˜ ‚L]&„J0๑•ู9/ํ9&ฬY๘ ่“ฐ{;๗๚'ภ3`’e@vH„yDZ$บ„3หˆDx28 ƒW€ Cx5xw‚B`ฃ$C$'ใสEl…y ีhฟ๋ิ€ DJ $(pฝ๎QA”A‰A–@'ว$ hpส0ฮV0 `บsพชeา$ษ4$"t2=fด˜4„A„{Tk–0|rHค๖ฤะฃ๏`L&ฑดsิhฆ]”งA<ฃก‹ฒ`Rด'ฃ•!ƒ‹1Nฆ;ฃ_Št3#  โ๒๋๚ุ๊Vใ๊ *veัF`E O$Ž{)ูW=p:ฎึF`Šพ2ฦฤ2ฺ“C๋มŒา^ื.สฤ‡˜ก๘–๘G<๛.p็Ne2๊‹๏ึดบ+Y์sl:ยรหผ๏ต ซu5ฉฆ๎ะtภu•^8พฬ6่๓ศ„Tmy๐Qษ%u~๔๒š%~1rา˜aw฿š^๙_ฉZฃZaขƒฒ0!ู่กท๚N๖`ฅ.Ž uqภฑ็๊YBฅ\™จ๓…„สแจ€ิ๊…[e๐‹๎๎:@ ๊J'E,ฏ3ubj@pจมฦไ๐ดfจษ฿๓๎ตทeW9( ๓บๅ ด…‰ณๆ=‹l”Gฆเ7gj โSƒM6ฦฐ ๔0ƒ9๒ง–Oห‘จํlงฎBผaช๒ฏ  ƒ<ฆ็žวBี™(VRApกf๙^ฐ๚ฏ+g9 qำนŠMฦt]ปุชp๋E•r@]‡@๓ฉVŸkVฅ u๊ไd้^ัX ๅ–—R@?Eี๖Y2๔จ˜ษ๏]#เวผš4ภJๅK๖มไึ'รมพdฒโPC|mใmๅnไ#พ‚$+48u'…็e&๛ฟ€[n[Lแศ๙’žฑ%{BCDฺL:^! ‹‚ำbฦ™:&ษˆ๎‚g3“-3ะ๘uด่ญวๆะ๐นƒb iLZฺ้‚W‚FSษไIdอ๑ก6.‘k5P๎„l7ž7Uz’T:NNก‘—."€ชๅ)‰ล—์’['฿|U"Aƒ€ณ—‚I•vฉwะุp๋™ํญtนdk‚ˆž9ุ›Šๅซซอ9nจD‹mqน—7I|6›Kbcƒ]ถM๔“ฉฒฤฮ๗—่ถBA€B๘ศช_๔JบT ูq ะ 6@—ง„ธF—‡ƒžhd`GฎT๋๑ท:Mล7'เ…L,้Ih—ฦFP ปส~jฝŠŒํฤ ฌ$ยกย„ ฤ3™hAไ’-SŒ^๛ฺ†…ะไ-%qeฯ์๋~ภฦQqซงฌln"iพž&‘ๆัQe?FlK๏จ"๚Asภ(3Y;"กLžฺิe€tๅ'ฤRzMœช1 0จ{=ๆ๗)ž€ ณK %$C ย๘‘9ŠM๐๖ผ๋ผ4c ๊€EotjอยVงGDŽ)l๑8“ฏ,˜\wภฅเ !%$ฟื3tษ TBz’žาด iUJา[ขวxgd„Br๏$ล!eqˆ’"J>เฃ )\~ก‚Š‰3(^ โ Rย€8#>ึb›ไH€โG'7_ fำซcฮบtDoAA฿ƒ†(q™B<`วญ`Vˆ๘้ฮซ”ฉึ˜ย*๚bฯu‹Pญ4v@—+•ส.’๎Qๅิฅ$V‚ก•ํ@C0 ํRขำP[‘z:XฆH#eไม ๒s >?๚EศWO>@Iุ$|sฌžiโ ESฅฒ)0AŒ?ฃ9•ab,ถ@Kš๑ฬฉo&๎ˆฌQด%ฌ ฯžLu+› ย+H|ฬฦ?ดNKฬ4Œฦ’ำCnPt่ณ 'OT๒œา.j5เฤด8ศvถwึœซIฅ&•+฿`ผŠyScaO[#กgฐงQงœ€ธฺdช[๎KฝI็Ÿ—`๔ฤŒLPำธ #ฐมœฝ ฉ)2า7aTƒไ๋i@c\‘๎ ยโศ0n๊Cปp๒฿–้‹4อตŽx๖*๑หะR”zีYโ„b‡ภ๚T[\๚kU™v์Hสˆq’pเฆ„IยํI๋ล—) ‹bB X”PบNด…štzํ 2 Iๆ==ค ฆณˆจร;}†bŠœเqำji†งaฒ#" ฌŸ>1ผ‚ฐ1ไA›ฃp1ึํP‚งOว่O—ux๗Q๔๙ฐนฮ Fฯฒ(๚h„ฉO'MDxสLํK$ศตœhๆ& ๙‹ฆะ1Œ๏ฤ4››Si ใrHJ’PฐtDMห;rMใ‚+”๖— *–เŸํุ—5u2$ชf3’K ฿<๙PฒL็rัc‹I)˜ึๅ^๋da> %ลเัณb (Ÿ๚–@,‘2f,~"ฆ7R;ฎEัŽ;ฌญ”HXๅ(๚นŸ4Š2Zไ'Tชฟ๖‹„”ฝ2J+^!#oŽ„›”Y~4ุ-ืƒ๒GW*๐!฿รA•0&8€fไ{`ผฝ๘เW๖=’DP8’'= ึRฆ gฉ}๔iP>“ส#นหๅน4ราEะBRYฎฦ^4e๓‚งN8ธV,[B‹†ฤจ๎D#X†๘]ฒ,ึ่โซLBsNC> +ขํoอ๊^xจ์ง  ๗•๚jC์.4ภYa‰_{eฆA2=rŽ๐ุ+‰ญดึ๎งก9POปA!! ื}ดYสPJe„—็Gnšฑ%x1ฌ/}RgHุša Ž^3-Šฉ ‹5 ถ|‹qSงะ๐aWK{ 1al`Iภ1ำ ™ฦQ้พ๋f_yyCZ)ฤL3Xฑ] W6@DM™T๘<.„ซu๋GฮKŒ์8ูDs๔ฺะฑWๆ‘r…\฿7Z\ีห๒Vำ"IฆขŒ๕Sกงฎdต‰>CŽฆศUj฿ษežษศำD ฎ3MวtWcP๏ฏ†–๒ำ‰6#3Qืšnแ‰ฉ…ฐJ\บกร7ž#็ฃฑ`ุ€K๋๋ค ฉืlV6 &ำTŽ’ ~๎ฺl.’พก <˜˜BP ฃ*ด!zRZฤไeั™ปพพูทTฑ#ฃC้LHฑฌศชWล)ฺD›†๗“pฎYU#ษส51{WJ€Žค4^ฤfณฬผZฐ๖ฬัy6–ำ‘T2™dฮ4H=๊B„าŠษฌ}œ&ƒ๊ย,aPรงv+:2ๆ~๒ม*0ผฐขˆdฆษ“ย๕ึƒd ย‚๘แห!"A+‰rHn กวเsAไมศฺ—U €๘คb H“N6$.ะlภ};ฝ@ฃกโiKร \ฌา‚:v‰QEว:,|ชQ  Y0|ว%ึ@ํฐ ไqc็ำัdqhฏซ่ซนvCอGV†ฐฉธฏ-(ัˆm…’1ปฎโq89KF–ึรค "2ฑ๒}Rrz๓Ž,j^ฅภqห\…–#pƒป+๕`flšณฝโ:k ดt–5E„OaIรJฌP @psญE™j1ไ4;6๖๔/aHฮ.ผฯำฐTXคp“L‹ธฤL8คฝF็„šiจlํ1–ฒYุŠ8ษ %!/ม๙{ญจาล๑ ้œ‹ูๅฦXœ€บbˆ•ฝ…NษยxpปบไPW๊ฝํ่ฤcI9gฒ*บฟ๎ƒนœ%:ึปLหuŠ‚CAOยŒญ™%ฌ/ล“ด(Yขณ^๏? ใ&I'ˆศuh[x‹ด–Q๔$วzาตลฝ๘‚ ฿ณž‚(=Vื€Ÿ๊ พm๐ญลU) ปํlฮ ฮ’พฬiœ•dใฆˆ™จฝ~fฆ๙jGํตึR{D€%>ฎฅ@”…”6‚ฅค1‚œ`ฦ!ฮ ` ล†ถwY๓ง‰ีเk/a0A†ซยนŸิดสYhฒฏ—ต๒ˆd์ๆxš‘k:fšƒ๏ฆ’<ีุูWL4`8IYMBมSlcไึ฿ศเ™-ปEา‚'ฮฺŒล:,Dฌฐ็ัู๘ฦฉ84ำ)~๗2ทj€Ÿ–ว iถทB(Lใต| "aฉšฆฏ4,ฆb8ง”ฅิ“i 94ฆๅิŒาjWั‰ฐ ฉ6*ะT๐ฃ†c4gŠฬ“ขืUMษbณRวEฒช‹๒…C5‘ฦ)jไศด ‘ญ16pbร๋ฦŽH๗งชFx๒ˆ๑นฟ—ฏซฤฃ%4โ๒QูฤC‰สˆงล $9า:ลM>๒EึaฦoซฬŸ^ยุ<IwYgq7s[†๐’๐ -yะ1ุน5†ไaฤใMKšืๆRBวY€†๓Fq}นโ็ฑ๔8๏ภฒ*ศNtๅ'.Yb„”›คอ๐Zอ๛vลK (ส]&ษœŠ( ี™ฅ2ฏ:0ี €ไoคฮั…PKiBH4U๐นX,Žข[ˆ๐$ 0ญmXฑๅปุด‚ ๒ฅf๋5๐0ฑ VR ฉ8๊%ูั๑สงงDtฐรUŒ‘s`๔ท-BPศz๔P”s๋ฐมฆvI๋ถ8z-ฅt1DiB า"หถศYTJ ƒฤ.š?ิ0ว7เ€jLขรNž๚[2tศยฤฎฬŽ†ๆ ๙#6?Eืป†”Œืกฎˆ:ษžฮY;ƒ็ฌA&q…๊SุIRศ)ษss 9*xา฿0Bj)m์แHงAพ๖hyะh๒Mmค&4ลŠ…4€ €‡ฺgธŸVๆ&tY๓ฺฆฯOCS0ณYฒ๔‚d7MvN๏j)w๋๗Aษ(ฌโo "อขษ[ฆึ E`›”Š๋7ezธฤ†ป-ทQภ]ฆ6+Bcaอ@^Iย:ฒาปดพ=ƒ—šญˆsSŸไฯncว๙ำ 6 ‚อ็OใกB„4ฝˆŸ็ LฅจภGpใB‰q/<•zAใำCมฅป €ิA~นษxะ0 6rihฃอhคI์ุทO‚N,:oวมk์็ฺ๖ล/๓ธ{Hฐ,zะ‚gฺfศปz—ฏำฮ€5F๕ฒTrภn/ไt``l†ม™*H6jTกtG/x๘ห@P@(„ฤI่p าeบ!๙๊`wv,:A์‘œนNฃ 4}09zวqC์ไ$r์M`Y†Q”’ ๘Mไไ•ซณ๗ฤ(|้B!ํ>ขมุ>ซO pwj A*@›ชŸJไC[h&3๋B Qbฯฉ8 …:๑“%f~v/‹lโSไะ่า0่ะ๐0a“ทถ"ŽBๆ8(f ๚uGoวšศgy๑ลืtฃ_รy~ภอ”Ž ร%ไ…ฐ๛m†L ่เ!I$ไXt0ƒฯ€~ภeŒPˆzหอ]ขU์g ะ=_ฑ?๑บตบ.j#+`li‘ก BŠ๑โM5›า บฟล‘GาpŒž7ซa †ึ’%Y[UG9ๆ™๒ฦำ@\bD๊ะY…’{‰{ค๐ED0ฎ— ึ$ Q๘+FvC๐`จ์3ฐQฅ “๎ฑE\เะuC9อแร![€$๊l๏฿๕ š๚๊๙6฿DจoูDgภGˆ*+X!ธ%#‚C€q‘ ?้ญ8ZUB)U@oขฯpgฉถZุq…กกษ8Žส9๘žว|ucแcAัœถญ้ฐ•W;ฉ@แ" €ุ>P‚ำ๏ๅh_™ว9}.6€„V/วO:เ3ด}ŽกZS ฑตœ{:ฺุ~’๓œลyŠkฆc๎ˆคO6;OฮBแ=—bV๑. Rฟk‹ o๑ ค^ฑGV=แุ }๏oI"+ ๔ ]w‡ืFชมzฯทไฎ๖`<“ษ๕ฒ30๛h๗Œล3]๊Rf๚—ฟ859sผ`K…M€ค8๑ห XปUqˆ<‰˜ฅ\˜“ฦZOเss่ฟM’ท&j&ฯ ฤึ๗ .ง%Ÿ าPสBL~^ไ฿๙Gˆหˆ‘3pแD‡ฌ•:ํธืฃZฟฃมนจฉ<\๑ซว iฏจWฬ†จศย"(”ƒ:แลโz‹ฉ–X–~๊ฮ0PG]8๏‚ผชฌƒŒˆRQMNTฆqfW~!0”R%ี‘0๐xvGFy/F๘-›ฯw๚u‚/๔๎*๋+’ฦ \๕ย8@ˆ6ขซยฉด‹ปc<ดเLƒ;c[š๛๗Šูยบ nr ŸQS'oฃQu๒Tฎ{qา_œอฟƒƒSdชปA*รฐ:m๒8Yuz2PBซ ์Hh`lดk’p่LLh cEb6eา‹ ?!„™>| *=VญปK๎@ž๋“rx‘0ำG`%ryr[6๊ŠY3Œ7Œล ‡f*—*n•เ%9™ฺdfร่1๓1ขฺ^'ฬ]‰š– Rอ฿่qิ.ุ๊๗,œ๖•‰ำ^%„ฏlยฺโ ใฝeุุ#wWยัs๓56!ู=โๅ!q[๖ฐฤุ ร%ศิฎ]๓‹5^:€ฝmจ5ฉ)?V b|šuภ7f“บ์wฐœๆฤ๐,:็Ye ๆ†R%” ๒[ภ›๎ ษo gๆFซAzตFPฅ›๒Ÿภิx›ถ๎{ฯํ๖d‚xรญึwร8Ž˜–ู”{{L> ฎ‚dฌ2CไL‘บจไL,œL่,„๒(เmS์๘›รุๅๅ$=ั|%ึlu่& ฤ…”83ใฺ N ขXๅŠx ๋ \Vn๕อ๏žJ[)I›๊wŒฤ/ั้นปฒฺ ฒ| GลบูุYืม๐๗DHต˜ฝ*›Sช๎่คๆ’p6ฎ0ำcJ2ใ@ลWด%ัฆc๏ผ_^ำ$ั็#*:Gงฎๆ6Žก๏n>อD;๚๙ฅ~ค`9๋hXB ๑UŽซJB_ะฒˆ˜๙หˆ๎%ช’ดw'น$ณ่v|#T<68็ชKMถฯ‘-ู5U+พถก'ํB หฤชN…‘๘ŽbJ์รOv'ศ์|‹„+*MŒผk(dˆ }›Cฺหฑ@ ณมqฐณน&๙aR%}ด ห!ึVะƒ‘s3w2ฆŽัaฒ2ฒŽawHๆzก/Q0๐Fฆ ู]~;žฅรคหึ ND๙P m˜K3xŠke_™ฃ  ๑๓Sฏ!กV&=ฒฒชฒบv็_PภL9ุƒYฃกiฟ งNUญำ_ถฌฏ)–ƒJ69‘f*ส๐S ฑ 17ฺF|ธBR$ษทy,ฦฑ.ฟภี&=uqsแ๓กODล๒B›ศ๔R„=็ูษณ—e๘ุฝษ‡เBฎฌไฌึHญกฎˆ ช2lu'h7^#S…)ฃXi2..Peก/@FีKษ‘$้](ุ%เ|–2ษ๐Y1pCฑ8tม๙I๘ใ11N//+\ป“pฅjิฤ๏๔d€”ฮแWสm้›รI=฿ฝทำY“ZxกิMะ‰P8ฒ“1/ JGซ๕ฤ^U ,Pศd1Oฎ๓^ฌy฿pq”lฟฃ2hั$๘jv๑๘‹ชญฬ๕žIš๖%‡Šจงแ่ิ]Vๆฮึ .'[+WU8มญ[Žๅ†D๊’ณ่,฿ปพ-=[ภๅูย O wƒ ฟE๊)ๅ3ฑ๘ฌไ๓ฟJ&ฅd๏ู‚ถRยกฌ—S–\.ฐ •5J$I&๓‘oชทHศณ~› l‡ฝฮz>€ Ux/๕H๑ะu;š?Gtฎ{?เณ;๓TกŒชH ไL|F๚8แ†๐ฒ}ˆก{๚˜p:฿2t์อ†aทหังp6ัฯํิ๛ทถ๘่5Y"LฺD“Œ๙.ŠrผุV“฿‘ฒ฿S_ บฺสkฯ]ปn&๚Hธzง~ฆ9‘รฆ ฆp $ง4ู‚”'ข{บ& ธซหM\Ÿฮฐ้ั‡จฎ!์qฎiเ ฯฤ(.hš'ห Bฑ TญฒŸ|ž{I„6cL๒ก.นžฃ๋นiIพ๊ซฟ\!เ;งเg`1โ แพ•˜j%C นo3*60๗ทEŒ˜ุŽž]tไ‰.ื-%0 Y‰Kว_nft] ท*VFCฦtJ’ฐฤTิ+ค\WZ8แ๒ฤโืgFะฒเู^ žfถ 5šI=ถื๙#6๖.@๕2zาร;Wš`B/ฤ™Q๘ŠgํฐhŠjyJำฐ€NฒAXค3ช,๕ค› Kไ6่’๋6ฺฒุM0ฎT@ Ž…O{กฃ˜4kjฃ|"ฌftัŸ”„U‚<-a†ฎีๆ5b๚)ํ^Rฑฐ8™„ฮมž:†งilพญภKaว6@ตๆ”ร!ภอร] buvฮ$ ‡oUวยœ~:.…L๛t–่๊e— ว€ขJฮพP l$S[z–~Rq39้’บ๔ถ9๋Q๏ห/m"•%สค‰ฏ†ผ7Ÿš ร๎5MKLŸ้‘ง"I฿G„ ่XTŽXLฟF๐งV j‘p^ก/Mรgมป{ถนปw๔ ์*๘บ€ž9๐—สOพสˆ<ห"a๔ม๗Aแ๎๏ˆqฟปข†.M—2@m๖๋‡pฟ^ฺ'“w฿•m๖วkxO8 $[๓ซ&ม|Y‚Zy`2_|%r—“/ๅJ?กQ๙รNฬฑl‰3รžฮ฿KกE$–wvCหhฃ๛ a@๗U€1ฉM๘พ%0?1*ฅ –$GำZำ{!|วสฟภ$ร฿•ย-๒ูชEv;‹อ“:ไฝ‹Ÿ`Blฯหธ ์Œงวษฌ›oQ0&‹•๛๑,†F?กฦไ„^s,‡c“™มhห•$รEcl0ๅบw`โบนลˆฉ@/€r^l˜8cTท3™k@›๚Jย”ดuPะ&สชNษ๓d๙JjTKธชi ท้*uงป้X–{t“j~ฝษก}๙๑i\BศKenโ‹ศต|N๋ึ๊ šu’๏#ร]@l๕CZ$iPๆaฝใธฉt04y20 sุึชโ,Auฦ!QาุBไฯ–๖–^์ˆ@Vsษ‘€‚\Z฿aใ7ฉ์พ‰ฉธโัˆณป6-T๏rฑภไU˜ เu“Œ~๋ฐ1HยJจ(<ฮฑŽ‚๒ฝำณbRิ–ธqi ฯ้ๆขฌJ?ํeGม ง*jVฤง "แุใ†:Y);-Fๅd๔!๐HฃปูG~ด•uฆx cb6m•ผ๘)&;๑0‡ฯdU?‡8ลX~๏Ÿ1ั2šผจt€ˆIุx่5ฤภ{(๋z„๊ 'ฟ๛[ ลƒkŽ่Zะ…ุ์้อi,๐ลb™1ฬ‡‹ขอแ`บฑ(๔ชmHแN๐์e‰Kคฐ฿ิ/ [เด(๕๖#Q๔—GdสuฮTฉฝ^๘mณขฟ%๙†Œล!(˜7Kg้…P=่h๘ฯ•แkษU+Œยฦ.[าeฏ‹ฯะผณCมžๅ"GDฮจฃ›<*<ึฯ้h ซ)ค` A˜U @O]hlลf2”…!Hž็ฯF#QBย้=uศพ9fดh€๓;"R„ฅฺ’K3-‚(G )ผPฑแ๘กจžฆฒT],7มec ๋ F4hH ำsณ73แ–Ÿ กฒ โ`เบR–กTํwfอณ;6B๓>ล˜ 9&ศโัฺ‚ฮ?’—๗๘)ภ\จŒ€<&Oฬ†™๐5 LรJuฅ@Yž,๋ƒฒฺพ_wย0˜^้17๑๐๐๖์pป‡*>D”8ใŸ˜ฐ_)$Uลบ๓สRด!jOF๔ึ>{ˆะ’‘ ปt,ฏ-…bPต,m`D"/Ÿz๐Aโ อ”ุฅ๊฿QZGี&U]xejxๆๅLwv๕~ฒœ=)@ุBฏึ6ส?!;53/ps@tƒOZS7ฉ”งุ™ฎ’nŸุlx่๛๎Z?แƒZ๙—j aฒ–{๛6—ฅL4›Šซ1Ÿ 2นด๙ฏ‹Q‡i&ึฅlƒˆแฝล์]o= 7ฤฤผ ofะ–r MEV@ƒโH๕ฆ่๒๛/่aDฅูฆ๋H•ƒฎฎlK5)ยŒZ OEดœ˜„3มฆIGฉ'ะณ;๎D'ถzl(‘ ๗EŸฯั$Š.ูœ๐-W R'\w+)ยw3ๆ๊บพ๙ธ @%Rิ).๒~Š9;]๔.ยšg+)ุ%ศ๖kีฮภา‰ณจ^า๖N€Wท>b1z:s†จoD K๖ฒบย2w[|>9โฎvWMFโฏu`น๋อax‡chีซ๕Uท`*ส†e๛]O V'6‡ภƒxิd?ผH]_r๕Aปฃ+zภdญF›ฏH ฤส‹<ค…วดฃีฦkUsFzภบ๒ฯaHึว9-ณœ˜ƒgv‚b‹=ำ๋L/žEป)ฐฤ.˜ฝx9j%Bใ) $—Œ้AหBขๆ์ ƒŸt b.bาAEจžZRb๖H(‘ฃ๕Jyaใˆา9Wj0fค฿F'›ฐXเzพร ๏$DQญ6ด๋žqƒุ` oสะ i=แม{#4คฉFYH๙@ุJะ3 3i~‘tYะขูhkH‡Pฯ๑ื๗17ำเเฅ†YD—"p—ฤฆ;'ณ16€ฦf—pu‹ ๔พ>F›oDลQiจnแฬ’-ฯ็ @P#ไšซ‡ ฆh“j ‚ˆล€fดถC– –ƒ7ยฐ”T5HVXษpํ๖ษklฤญŒณฎ]™ฺyXrฦ)?อบำBNJš Bƒ๗๘ไฝ#ห›9e”&&ป_0•ำ=ฎšpZษ6งชhคู)๐ ฬ—ƒa bภŒํ=(p)‡โูๅํฌ;Š.N•,“รWํ^ *hิบŠC—๎m}E™7i๕เ‡6ม‚ร๗aหIํvอฒxpƒ*Ac#4‚๛Œณว N๖&๐`)ฎฤ‰ˆฃHฃWe›ƒy7jlก๏ฐoEh_n3 ญ ‹jp?ช4่p2WภE'kT_ใ &ฃฐ!ศ–jVl˜Hํำป_kษšแศโ๖สณ๙aY๙Œ ก sฮ@ไ[…G"สำbYีLซXฆชi ีC˜q8๑&๚zVaY{่ฦ#I@ตงญ2˜Žm๓!๔dŒ[1 …AเฦขšnK่๐๒ีฦeืฒๅ/>฿dƒm†uX:xสท\„โp๒N‘๛ฉl+ƒH+cวtSวถ‚ๆCภฑ[ํเ~3Œอeภ}6ฐ \ณ,ฮ๑ษ„˜ื|คY๒ง็๘˜งv]ฌ'๛|ฟžŸ๋&ํ–์M๔2ฐ d๕ฌdsเx-((76”ฉaX›ฝm=ี๎ำŠฟใQˆ—<$ชช€๓Qย†˜„บ\“ ิ ชqi้H้˜‡ฌ‹‰i'iค”$"ฃ{S*VwF‹“/ฐt<์สมแQ`ส’Zธš+๐pr)›(น.jต้ธซIk5๕ <ไดส†ฑหฎษึ, kO‘‡œDTˆสJ&^7บฃ‡ฤชQคหฯv฿e &žZุ’ ^4๚ฦ^sฐ๑D+`WHต™ฎb์6ูฤฉ˜ ธฎศL˜W{ZZ ฆ@ฐไmqแ๛vฆษท(Dม\+ิlๅ้๛0*ฅVฅ฿‡ฐVmีŠงๆhฦๆุ/S`|ณ^\<-™„ฉร6๋ธ2ฉN3‚" ToŸ๓Œlrไลe ‚!๕ลH2 ‹pƒA ึ›จ ‡รฯ{›ศผ/ฃ๒Ÿั็œudU2*2๒"cซฬ"p…${ฉ€y, ้ฅ‹๖&\เmพ&บ`ะ|x ฆp…ˆCชาw#ยษ๛W9DญIi๑ั–Cˆ›Ks๏–็‡Sถ“ใ3๏ ,ถ‘–M›’;jจB๋ง4š›P›2ููiต๎๏fถฎษฟํมbAญ]aขidํย๐Šญจ†"ฤ๒ืi!aQhิCNOฝฑžŠ๏‚๕Yํ “xF$ฤ๘g—9žฅ‘Z`Wซฐ…VBโgธฑ ์ฺ#j\ห‚—จ€e๙G๑๛ [ณ.เพ]‹ช0บ~X{2›Dฉ„? Š๘"๓Š3ฤBแj,ภK~ฦb#„0ฌษ’L˜kcอ(6 ธ  ฑaE7ฮป/ีŽฏ%ญ ฑœท๏ เษฤกR๋^J๛๋ลCฯZ+71XดะUO,ม„๖แ}#-”eูค 4๖3ล‚๐ํtร8ง™Z7†ฮiเฌส<:iั?Ft‹Fk–C W'˜๊f0i<โXdj—๙Œ”0W#i‹‹š–eCฯ zI7ˆฮBฐsทฌ˜.Kƒ  *๋V‘ฐซd‡ŸDlj๖@์ซ๏% ฉˆๅ ฮZˆฤs๏ฎshฬธ%‡^฿ ่๘อ๗@8†๒งค๘ฮ?  Nบ8gิG๘grจXฐ€Sƒป ไ• Ap ‘ณบ4‡z*๖น4แเง,รญนฤt4Gูnิ่๗‘ผ๕dSๅ>f๎”Q–C๒ฯWUZ{Sู;Nxๅฝส}ฐวH&บผ* ญ9ืธฏq‹šU1 ส๓ a `(M-aศG}ีnถฬฝ่ขฺ0 –ผpสิวmcn๑Ž ๒๐‚ษ˜ภ_๚\ฑlผเป}ฮ ศ 9๗FแvžHรพkƒJZNO ๅดmZแมQžําค aS๛๋f๚ )QC+2 d’˜ก[ฏป ฬ๏ม๕H"t* มc*bฯฦฺข๗ฏqฐจ,ต™๓ใ#S˜Ÿ#ขไu›'าฌ๕:4ฉasฅฉพCDMFง|ษธmฉ_ร1L]๖แY˜ส\ภง*คXœŠ>t๚–ฏธฬg‹งุD‰ŸฃŠ๘่d@&[ฐ)8รฮ;<œ{๓ส8<–ศ+VG\ฐH˜ฎฆ^ฏa—‹ae-4”๚sฺJิA \เ hM[‚\`๐“ผ#ฟpD5Z97g;ฒ๗BWโmฮไ‰qTXX‘%0นvบžใ†๙&๙ท]E น4]๛FŸIJไค๙–œ„&SŽ_ฆ๎ƒ4ศR‰0 ฏฅD+หmeนิจY ƒg๗ะO๘๑+M{”03ฯv'อ…f…มลtฏแ่ :;๔ุฑส N๔ฆnเ\ว”^,)1lใแ’aB๏ZZฺ„[•เ ธ ๛ZSาาไผUYh฿†ฯสw€๕‹šSธ\ฎ/ค*?zQะ‹`X4๏gนrํ[งŠCWๆG๛.งY„์0Q|๛Rิƒ‚E[wพฆ๎„yƒ)ธแ๏‘,ัˆ๏$หNK@c/b -#ZฏI นG$ฦ—ฏ™‹tm็สH#๊๐)XฃwPZAD|ขS ofŠ๕Tค€Hฺ๋)ธฮไำ็ว>ชM1 b 7แฐ…ษ†S‹uะรqื ๖ท๖jK4[s๐„‡ •—ืxL ›ึผวขฉ๋]5 ๚!M!AพdฦงN ห><ซ:วปZ(ฐ8†๘)e… „฿ ปฅ™†/™Wุศภ| ฐbชุ้ˆ๚<้๗œƒฎT?%ร ฒ:@ฑไิ,-เ๘€ecMP๐8uคmฐVะgŒ9H้๖6ฎห็‹}พ=ณ5ƒ—AbาฤŽๆฯ์ฐฌฮ™ภV:’…_ leษน฿ ฯส๎–•v `อ0Žไ!$`Gม้A"I;$฿^?๚ฎŠํ‰Ke Oข อ๗ณN(ีฝ็๖“Yyส5B็กwธะVน%ˆju;)lFตoaๅ์ห›7๓x้’ฺธุ4-‰ษ%๋ †๐$ฯึน/zskว˜(sh>ปมDDฉลƒษtฅTฤ7๖rurภœธ0ษาข `ดh5 5Œฆษไถ“Sแ}ธัศา4hrvaผ้l้c!ZjB]นŽฆฉฮxโDฏถb–TxzYS‚฿6_๖)ƒสoฐิp>˜#@PขSำ*bS\q ฦ‹x๑YfQ><"๓ทฒใข Y6‘ล๒IEr_7๑ˆาฐ VิHร!ณลI็rŒEL็Ž6ž!Nป๖q"'’d “aqMvบล‹A‚%ํ๕บบพ ๑vณํฝŽ n๐.;š๋A/ฯ็ฐ๔2สฒ‰œa8D$GWv…#ฬ๛ 9ฎkลล'‰หoุŸœo€@โฬ (]gkํ+}/ (nq‡บ์K(fขอึ ฦŸะธžp๘ฬ2ิศ3Yฐใ้wฒpDdGดq2$ฬษ}‘KำฏAญ"๖E&N‚tg'Nesี๓!ะฎ๐4q์œo}์ฟฅSตต๋,oีjr/s œTMT—&๖ะ๐Qf\12กh'&ctNฆ๚'Tลx7ผ]2๛ ;Gอ ส…๋ขใ|Tช++:%/ †่ฆ๛Šณ1Tย ‘ล๚ฯ“ ๓ห€Ÿ<ิ๑ฬ4ยำิ๙Šภอ”ื“ห— ษ,0~แ๒!กW‹Oฉเ'‰ แอ:s๑uศฦสาฆู†๙(ด^๏ฎŽ ตฅœย)˜๘7ุ่fม€ัmlฅ๒าน๎1ลซtาZƒ่hภ สL0 งฃท–6าX"Jา‚ํ Œˆ4ง9ุั ึฉB้}ƒิญ`่`‘ฅฎ๐„ำ’็ #ฆJ๏n้ไ๔ั_‘Fญ H|šก$OศKฮ๚=กล“i1๗กฆ7Œ”o-Hหqกช๛p[ษซ%%:ˆไ€ษˆi3 ๚„G C—LL‰4ŠSะ:ždB๒j|‰ˆpYำ๖SDP>ถpำvฒ5KLe่{t0ฎ๒‘yวEND$เ*;zป5šŠN’แBI๓ูgnŒ€.Nษ|ืถเัnะ”RศaSคZืยJcHฒ mอัX๘ส฿ek;_ 6ศ,yสยb”0#ฆZ„ธA e|w‚ิฬG Uฝ1lธหLDุ7ร„Vฃq’t[ญxuE”QULˆ๏๐PBlZSh–’.แ้1Q0U์ูฑ8R„i๚p;ฆ๑{๓๔H#–GON!?๋ฃ่t>ฉQ |pสkค๓จq!็gT,๖ีjวะ2รศsว4ํˆŠt”jไทnฦ›/IษO˜E!ห‹nF๕›4จ†ทˆM&ิ1„’—พ…xำ$งew+v™S๐ห  bm]e%8 ฒไPฬย !๚๏ณŒsย๓_06ฃ๒)ยQด2JB†ุ๊ „[t9ฎ–ƒ'”งณิœ,งขฬ๔[ฝfร†ื’ถ]˜ยBŠBร@šฆ๎r&B„s|•Qš ฐง™ืgจํOCˆž1‡J D็<ฬฯโU‡ฒฮผำ(oฉ!ณhฆKฝH๊ 0q›ˆ’งAัVˆผ'pดfฬy"Q O… 2วZปพŸqยเฝ#d"›@bQป,ฎ“ย w)๎Pอ\b`xŠ฿O)ข dผMC€$[HoคWฆัva4{ไวฑ`52ํบ‡ณ5;‚…XฐaoK†;˜6“%มR(ภ‚Œำฦั…x9Š8 ภ2rใDc๗ฅ@ูˆŒๆพษค๎Fื<†d(ศAN#FIทž›zmE‰ปF=ฉฑ…ฦšญๅ•S‚€f 4ส8ง<'ด„ํj๊Ž๔-ช˜ฺ'ว˜<าTb๑2v€Eภtธฟqกา3qODd_ํะ{`/œhh๊‚๖ฬ`ย’9_1hAY|/๙๋ ซทU๊-อ•บะฤA”ีo(๑ห๊"“$rุ†TฬืPR;ง.ธ-w>&LJ๘iC`Aฃ^ฑ—ำ#‰€ฤX8—t—๖โH?€dมฟaรฤ–TSTฺaจHŸ0@า๎Š๐๓U)ˆฃๆ๏^e}Jb7%ื”%:›ำฦฟ@—ฏMŽ+๑ปy”sqŽชก๋Lฬา๘ฟแ สYช00ร”๗GDก >ฤฉ๊AW ˆถ๐2I:ฤูF žศวš3ใŸ2ส ํq€๗ภ”:6S•—Žั]Kฮฯ" ฅฎg[ฆ ๅฯ‘H“˜โBฌ5ศVEqLJŒ•X{CผˆนงBฝลูา!ฅPซIแq9ป๘Llx–ฎสช7า>ึค–]@ี!@9H”!ชํไศpภษ™ี$ โ?ี)›ซŽจlฐ/"ฑ”ภฬ–ฏ+“@`}}:\๗•ฏะ 8•zQgSฃฟ+๒’คฟม’C„ฃ}€R:Ÿ๕HUF\กX’g๖ภ/โ๋€AZ%c1ีwlET–wX  ZNhฅ …ฤyf2Dฦ €รธ‰&vฎL“q๎4ฦ7•๑ง๛สz๚๐\iJyภ่J-kฏNฤ3ฝ ๋ ฃ-ผs‘ัJ5‰—)ูV0™N0dฺ\ำ›d0d-ฉใEฺ[mfฃ\ฃUmมxฒาาCซR<(`ชั•ๆƒp4^!hิQ่ `ข๙!l“ ~ฦ™์:J‡ษ ๑lWฑ€9หธฬZXB=๋ศl)`jžชeVJณเU€ณ†G!ฎsุ็1ิ?ฦผ3„จรŠ.ณ}bIaู๊6เส• œt?่ภ€SxZJ'รp i๋,ฆ.ˆ๑ฌุR2T`5˜-R BxrๆWH๖ JPฐe#Bb‰|“ฏ”-ฑก์‹[ฒ„ ฦไPยโ…คจEh‹ฑณย‹(5Sœข•fีrไร/]หฐัIฦŠ ฬึd”E#๚ขOฎS๚3—9ำป]ธบณ€e‚ฟฎีษน.9_Œb๊eงๆพMŒด9b#eฉ(’ฆ-ˆ 0งาืRaฑเฦ„9๙บ–ˆ"‰ฑU,แย%๚~พX่€๖—”๋z€ฝ{'6[@„t[W%ั* .d'vR {”า๐hŠฆ!Aed’CชE}ปx=E[|๏B$7Jก* B-แ ,=k7”[_ถ๊-ะI๔– ขซ€’‡J5eึฬถฤด{ ศํ( ด†;WMwง`ซฐ€ห~pA ฐz 8‡๎fๆ))โŒโย(@ ฉฤชู……ู<แ๎…ไ.a%N ๒์n้@bzญร‡ศัภตฟ>ภ๋๔%…‡€T*?lgbฟd๖ศ<‚ฤตใ๚ภw9Naฌลผ8;<^*%›yา:tDฅา•Z<@‰0ชจซไ‚q4ฑไะํl\ –†1†๎ษŸำ`/ž$IJ า“sN)ผ;:A;’)$ื• ฐWwขy%KrIv\bถV™ฃ\nญd{ภ๔ศ6tปvํื/~ข*Oึํ 7U>ฃ8๛r‚AC<บj้ŠEโข-j็ุ็‰ทจxs๎)ฬDข›–1ผรŒ/ฯสq“p**ฬธภ$ู‘, แ ƒณB๕ศผpฤ๚k MhpˆK๊7ฦU่คร]๐๘แh&„-ž$ˆ้Žปฏ”“Y๊ฃ;เqห้6w•z๗ึW๛๎ห„ึญAฆhฒ์Dœ‘^R๖ษ"ญฦs5f๎w ˆฟไ+็Q&’/9ศ‚–œธwNbว๋้ญใฐุz{ๅุ•ำYล> ]NEฺมฑc,฿ž# BF:0อุ/-EศพฦยŒวืƒ๋F\๊Œ๔ไIง{t์ไAปZ‰C™ORรukฅ i๚”๔)…ytkdNธ&›vงAฑ™ˆ P{อึ๎๔หP'š๓’>ศ๊เxเฦ†`.ไ%,;:ิฟู:ŽฉซญฟํaF๑งoTQซ}v#๔์šืฃ‘๖ฺQk่ฦ'sฤิำึุ๗~…ตอz5hMฤQส’แY>C…่ส™„่พiทฬUฑ ำNF#J0uŒฮC๐ไ๐8k“! fซ้์งv น{E๑/ฯ๋ๆIKIEห> บpทyd†ฬe สพ”=z๔†:@7ึJเ๗ฤณฬ|ฦฦ5g8ภ๎x ล3็OฑŒช€ฤ 3€H1‹๓ฑุ„F. yดfzด์WIM ๑ูƒฦj[.wๆ%„i?า†Uย่ฉf|}@+[8•k7Cxค˜S…ํEOฏp $ไ—แ็พQๆป+™ส:ธ<แ]ถธKโ3‹T-yฒยอ[Nัึzดต„;yณŠค-HZชY^กิ.ฅM*ิ'h8าํA….ฐN๏2r‰œLBœ 7:Or’ฉ}‘C‰SหšS9ไJq#์ฃWI}*8หD!ˆธ# g#Y>8`• ์ะ’ย๑ ?a…2H,^๑ใฤ'ƒ๏?ธ^ธˆๆงnƒhใOฦ’ญฟi<ัชๆYa2ษ+™ฬ6aฐFฑโa<ฬ!„0ฌ‰2ฝ]c:๏eผKคฌX˜X˜[Ug้Oฏu5iิyPcVูTบž5RI๚ŸA6๒Oิธi คCŽ\‡๑…—ใQZM„DฤฦƒำฺฯB!X–ฤ:๔ะใ\!ว^ม…"{กE Vax$P \$ ณDBBTำFt่Ÿ~™ร{O‡ผ w๏๘ž5a#๘`ซ=g€ะฐY2>‡ฑMGฏ-Gญkรจช1Tพbฆ…L น`*ู€ซVฌX ญ*ฅxชeยงึŠZ*c`ชVมSีbฅบลJUŠ’ชะ*6 TK@จzqPฤโถษลh“Šวg†ฬ*฿”Uง(ช๖QU4‚ง9L’ ญcMฦ*ŒTปฉR!R,Bฃศ…EฐŠˆ พ*C|Tz๘๔p๐๔F่๔@่๔4่๔*เ๔เ๗๕ฑ๎ฑํ…ฐ–†ุฌแฑX๓bฐ€L€.™T2y`ฎภUpbเๅภ๋ ชT, %@`ไภ ่€# ?@t€ค๘GLˆ๋ลžSภ)๖žรฟ z“tฯฒFyืŽ 14LhŒะแ€ƒf™ฐศeภ(.)pK€@\โเ —Xธe@Tb v•h˜Dภล&๙0-IbD‰ d@ZD1ค@ ‘Dเyภธ๓€ังCN| 94ๆุำš#Nc lยภฐ;ธ, `c‹Xโภณ@(„2$0 "@- ˜$่B@‰<$ะมภ๘8p7Cฆ €เb่(@ฅ PA@…F ธ0ภ๕t‰œ‹ไGญ้ิOR—‰้ฤฒโI๏T๑ySอMW52\TฦoRๅฅKV•0ศŽฌเป‚Œ( - $ฒ€’š€” ค!6ฆ„ขwˆ๊Hขฉ†ฃ๙๚G๙ญ O r~เe~/เ]ทเV~/เP~7 Sz๏ K๚— Fv`;๖ฏ`9vั# JคงBอN‚,ไืฮลำญฒ'ฐ`ก'โ‚`\LT๐ูApBs)r…!ี โ( าi‚`ZoneMinder-1.32.2/web/fonts/glyphicons-halflings-regular.woff20000644000000000000000000004315413365153155023023 0ustar rootrootwOF2Flฑ\F M?FFTM `Œr ‚ฉ$ๅe6$†t „0 ‡"•Q?webfeŒ5์˜€๓@ ย?พ ฺ๖ˆ ฌt๖Ÿ” †Œม“วุ,3+2q หFฎYO์&>ฑ้ฑbาmค5ZŽๆH$ฑชYŠœฝ{์H jd† ี‰ ฒ%๗นูงy"ง๗๖ื+@นŒ]ฝซeŸ๛{๚า๛v Nc‹)ปn๙“ษ?~?่คh๒ž_็&iฦสศัมค‹?บ>ๅ^K ๛vด-c1ํ๔€‰2K แyท๕,'nไซเ(๐3EwiเB‚ &ฉ ้ฮTดlhุ0Mาฺุา†dๅYุr๑๏ฒฌณnti]yur•ดผฎ๛™๎VXsjกขžๅgMnชำ™HW•ฉริ r2๔>iT`V7ธอR(ฑจษฯเ+ o6๓'cลศBฐๆ๋4็ทึฮน‡ƒ‹ใฟšฎT ]a[Qd<3wq8,Ž…๎rTIก8แŽ0>Eธ?๙*Eจ็—ฆ•#ฯ๚7'์†S oc๛สทํ_‹7&#*ั+)ฝณ ะ+4aฐA6ถcŒ็yฑูฃ†f(bF้ๆไ้ด$;{ YAร1vP-tG๘Œแภฑอ"๛ฐ•Cยf- W‚๗ฎšิ™ฮuKึฐKใ#ญํไฌึ*K†< (ั฿๋ิ๗ืZ`ูซ ๅต[—%๊YTŠฌ{%ฏษŠ$ˆญ s{o๏ํœ๒ˆƒีป๏vt"pเœ4`ศ๊฿ฉŠฯค}o `ใณ๚'ne> ไG5sz๓_N๓ ’PKำฆvmU ษพ{zฝ๒๎๓Ÿ๘๘Œ‡"3`l ’–W#ิฝ^@+,cนษkoฉAOpnuตงิz๓–zJ)๕ฮฅ๗˜1ว}แแO=ญถ€xพRฦฤ`มJ‰`งqƒฅ‚Us/ฟ+๘kาvว1xl฿็jl–El์\nDŒศรฦถฏV†ฑๆะjg›{Zdๅ‰z7 5฿!xmย5oย[ฝฟu๏๛&ทฏ1ฺ‚HBkAโ์qr๊ฮRฤ ฃทฐ(\gh๔ศ7‰์าŠy=†HตZ๓UPh้ะ$8Rgำฤ€zยgอญษN:‹เ1uฌ$…ฏคกž>R]ๅ๚—ฆฌ"Ÿ๔f7ผ— ๕Kฏ^'˜ช๋ฝ3“+E/ผฤ^‰YU5]จNB.ส‹ฅา8฿ล+ฮอ8๙,|‰{M|ŒAŠๅua|ะaˆ’์Žีห…ี% lKGขPํ,Nuๆิkc๏8mX@ฺำdฬ˜?๑ฺ๓ข็Yำ&ฯ{๊๖”๘ณวฯ?P(ฎGŠ]ถฟฯฦไฏไOไไทไืไ—ไr-น’\LFษ9—,&ง“yไ8rฌ๛ด๛3๗Ÿธ?pฟ๏>๊~บ๛s่ไ‹žะขำDวคz๓1๚ก?\U5q=ญูtัzิ’†&Znjข%้mMด"}ผ‰ึtkDบwhข-=mขฝBํ้๕76๑๔ฎƒ&:าปษqt" ึ1:“ฌ๋ะ•ฌฉu;๔"K_ก/Jdึc0–lฒ๕0ฒล'^Bถ๙8VCถีzgฐฒฝฌ[ ;ส๚d์ ูYึbศƒu‡ผฉu;œ@ž*}y‰| .ฉฌ'C>\g=๒9เV๋ลฏท[o†|g๐ ๕^ žถ>Šdผ 9ญฯว๛ไ๎ุล๘” *E|Aฅใ*Mญ[ใ[*mO฿Q้z?P้n?R)YมoT&[ฯU*‹•5ภSู MB ์กฅ๐[ณ ญ„฿oYDhญี{์ั,}1f?›ซ“NNœข ]ิO/^;\ฺยJส ฯBวEsJrช ฤš๕ฦ'๒…g/ใใัB%Ÿกo C๕๊บn•7‹:|๕yƒKtฒ&๗$งุsฏ|น๕wPˆ๙ฤ\i]พ$Z@+อ ถถี€90x]ปrธญ%พศี+๖RUฌEm‡+ฐชฃ;w uฌภ9/Iผิ7ศ7ีฆนQlu\ฆyะW‹N)๋8‰ฐvY๎*uดm”กฑฟ้โฉโบm( fสEฝ๒G8ฒ ขj#IฌฝRŽฬ๙z #qธ฿ท฿ „)Y ื$‹ แะ›†c_%ฟm-{!0-` ;แๅ…ฌ๑hyV๕ไม]Hv! ฯtaฝ\K๏ลฅ[ฬ1{"็j 6@์–3T0%ฅฟ้ฮ˜"วิ™ฦZI†Gไ›S“๗‚.ณฤฮฃpฦำฌSฦ1e้๛ู“ฤุ›๙ Yมvป8dฑ\ฑBกlกS๛R)าำ†็๙ฏ–ฎ€ {IŠำ†๔า%”ฏ>๛0ะŽฺฆณ\๐'”cgฝ2%4 QŽDก 0อ’3Bฒ"ษMŽีŽ&€ŠhIวยฺงาRgทMEคกฉกถš Iฝฮ(ฉี5U–D] }๒™bํ8$—‡์8จ>๓แX ฒh•"lตฮ€โj.%ˆ€HHว- Išฆธ#1’C4๕Y7„ํ๎ฎํY–กV o>Pส]ก6ฟท˜๖™O47f ฝ~AJdYFีย€ส.–o๕รy) ฦ8lฦ ถ22ŽeŠœฆา1Hก[t‰ฐ@!ศ… 2\€@ธ5ฤู“ส%Zื๎k’a๕ฎ@๚.`nจ3สOFŒขR(๓…ฅถ๗ฝZkLkF ํHWjY Iค๊5ื็*๑6ฺฮeตSbk.คผ5F,์.•N0฿ิ™’น€|”†Vฆ€||~N๔‡( 4ท์ฺ],์Jp|~๙xeำษAจิกฏ5ˆˆ/ปฺปSคไ๔”ะv๒ืy?›๙ฒ'_v|r๊„หX๘๎H้Qสฐตฤ“B@= Xฌ฿๚ฌB9ห4ณ˜ซรTฉุB‰Bฉc ญมซH‘Pฃฮ+‚๒‰_““ีYHู#ฌ$ชหส` ์F๘ฃ๑B;ใตย+”…BPRฐ4ฬผ tญ:t๕"ZEบJ^!Xยว“เฌq4_dTW(5ไ€งๅไธฺIฑ”Uล‡าAอz@U6n.WGXภม่H์RKภ&'swMฑjŠสŽ˜‹ฑ<Ÿ”ฑ˜3ฆ)€–`#F@  F ิขเุvoอb$x ๏+ฒเผu๛&ด}‰|สX&[ูช8F‹-นE&/>Š/ัGล.a๊z^Š/ƒิ})œฒ’๓'“xฉ‘$O=<วยz™คoไงแA9Mุ&๒~๎ก™น3rต3gŸœฆ'ฟ8าฃ\ฐ-ถMDzศ่˜๘kบอ5†๙ฑดA ยชG9ฉไ|1-ุ! ว87๛[œ๔ค,mR๎u|ฆ57ฎ =X‘ค,˜aJงู›ธ^tดN›4ำุ\fะ„]AzH^7ทชF•่•™„&k"LU>}์>็rBๅX(‚ช๋‚Tš%ฟซ Jชฟ„ญdhKฤ”๓ถP“ฒKมTFaA‡3HH†C[r;a›ภถdท™‡•54ศฬ ‰—lL„kjG{ก์8Ÿh~ไ fR@ะ์9wเBจคเะ0๋ zS๕๔ฐโ'— a7‚@@N›ฑŠฦนl‘bj3hN๎ฒX๖‹F/ษํeขsฃู'๚ŠDsQซ๑jwทฬ}Jศษzง^:V.:ืฺ‹{ฟอผ(ศฒB๗ๆหษฆ’ษ๓xŽ<Db#"Sฃก{๔’P๙HuฝN/๋{r6;wUถๆะ๒sึPะž“<ค็XภเYโsิฤ๗Mxuฐ‡\šb‘ฺsธ$Ž˜xŽส(ข‰/^|^*0j~mเฌ;#ท%J„M4วp˜QMืฌ๐::b\C2gfฐฐ]ฝzาP8T™ Uช“Qbึ่tบƒCฝTŸ> p 8+6g_2–lฮก6งH๒ ฺฮว†H:๗ d›<ๆCอ้6ณฏุค๊/ซฟ6ใE:ยK‹”"ห`kJฉ<›ฯฦขไ=๙vž7„ฅโN5ทต`ฐ๒›ถJt‹ู\jน6อ…%หž7๔*ฅ'ฅ•U•ู4ฑ:โX+ ไ\b’E ไ์๔afฎŠxŒ“}ม‰1+p™‹Bฅห0๎6๒โ”ๅž3r๕A$Nš~ถ๔#ณdช}วืคŸP7h๗H7bฃFชžยง…‚ฌ8๐ ณP>๘BtGNะŽข๐mไ‚x๔@j …‘ธ|{ภs9เป=wRู/ญซoDJs5z>“;Š'x๑Eะq^r้^=G?ฝ…9A๊กๆ–วAไ_—ฃํK%ตDษฎ:uikjk๎IeษๆฝฌG๚ี#*ก†)ตjm‘แ|ฝtฃน}`JๆฉZุˆ้ึ๗H=4๎{g฿ขพ)กqXˆMA,šH๘Œ7๛1‰๋V"ฅูo,็Y#h๗จS๒_๊;‹อa_ิ—Z^cn4ขญ ‚HธEซฎ?‰ซฐ}ั ศกปญ๎ผูค=}BžWvชUe๐ๅhšƒฺGŠ“F…‰‘ๆ;@2Sฅุœ€@หf ยหnอโ2ะ#Žๆน๑fY:]ฟJyๅH]ญ•-ท˜GืŒwgv'กโ|ˆฤ0e๑ ร_7ˆ๐าซn+f฿ูธโเY<ซ็(อ ช?๖์’ฉ‘y๒๙ก%wmฌ+jƒ&&!่ขc“^กu'b&๑hŸm6ยคšฮ๛น*2 ?ดAวIซถฦฒ5FWพุ™[โฦœจB๕Uz๏IฮE”“!’m:‹า๛œxhิe—วฎnผz|]% mูrมUธFฺฏ”๙ฎ1ฦ ‰};!n Fพ& g๒ฺ๑P‰†ฏฏ;&ื๖ฉœ๘๏$$ธ้Fซ).t›BํQจ3ฆŽฝ(žC=๚ทำไฆXภฮุesิ;i๛ซถญูŠ@ผั~NํษฮกE๋ ิSR‹กˆh\้่ฃ๚มBe๑oบ†ฝœ ดนฮbTั„nฮ’juนพภ g@ไ๗'qQ๋”Žnx.u6bVU&ิ ›]น;๏ช!C_ „ ž5ฦ*zนษบ๛m€RQu‘ชq‚’เฆฑPฺZ0ƒถ}m๑ผกฬส๕nฝฆ^nีOrษTฆโม‘ตฮ:ŸUใ'ืhภๆง0nZกp^R้|DFช_b\๒@–ึm‚่DE๋8ฦ{o่GM‰แ œqธฯ}๎Sd ฎC,๛iภšE๊Š้/ฐร‹[d8]Žื,MCIšฮะ๓_u—,]V™c๑"—ไคpฐg@์`"y)‹,;Bณ^eญžlฺกช2'€.(อะฤ˜โy>เ-|ฮhญำฤwš๚๊;ฉjๅฅ่’ํีัาiิฝฑไฏ๊_o|!@๖)ษขชฤ=ูฬŒSPz—้ˆ*!z})ผ|ฦงT}ฝj‘†EฃtCฌZๅกnŒฝรฝœ*ีž๕ษ4†ืฝ[นฉฑ พ9ปะฎจŽ่ˆ‘’“๕Œz`Wme๔o‚‹|j8jญ๏5แ9ผ๖ต@.๚šEฟVฬ/ฬZW@|—f_็\"${แœ‘vกเ๒ƒ๓œภศ/ธ๒ถ;aื:Seฆi3TไGห*๋ฮƒ]ธฦก/ฬhภ2C32$ฟำ๔ฟ1}ฟŒDคอNXษ๘t์?Fฯํ~n,Pj9.๎–>ืฃจอ{ 9ษEN-v|3h†๒‘CละธE”•‡ XTเโห;P–$ร=Jึ-ีg•๙igz~q—(A้<:h1ฑ9ณ3แN์ฬฝ๕Q‰‹ใว}CLุW๙฿งยืŽ~ƒแ ฺb™ฅ"†‡อ|ว4u}๎๐ญืูc™y€เ‹ศ6ฐก2[ ฅุึ\dธ,ตาŽีณbk๙คฬDๅไพ%0Txญฎ{=;๖ิท‹†(„i‡ ุLSท๘฿1ฝ˜๘ฉั3Nฯh/ค6?ๆ'E^๖~ฦิPฎ{sZ์™ZำK”ฤžB{’Dฬt๘&‰ฝิz’ำ๗)ตUoa๙5Qฆ3‘ศ—ˆr~ฟŽ‘พ ฆ๔ู๙F] $บ<่๚tm(š} ๛รฯMB@‡ž[œGx์ฮFำh8›#}ˆ๔,ฃ#ภ˜u Laz(Qhฑ4%ำxm`Uเ•ีน.Eจใv1a’ž4_'/[จdฑ{FxจIส59žŒ ƒD๕<๊์&8VˆE๓Fgษศู ่Š˜#๕I‘ไŸ2Sฒอ๎_ใฉ]QqAšnˆั_๐Q็>b˜4gกจญฑ-ฎ0&E#cฆi8ฟ vR/ี4ržŠ๋šP7•ฃKsOWตN3ีvE\bq†๛Q฿5ZนฺฝVy5]ฝึ๖เh/ i)ล๛ฆŽ-/ดฐใกkตNฌัŽพศฤ#e์)"Pํ๑ {KSQx‰ฒธฆ>aใ&ตํถŽ,„  _่gฅด๑-mแc<ืn]ะง-ฎ5‘2cจนกฎ๑z ฌ7d Pœz๓ๅตเ๒๐ฒV„๖“๛ฅOPvfผR Rคำฦเฐ“9†Z -Ÿมd†๗ฎšˆฟC›š๑๓ว`,๖atม=‡k?vขํ4#P ่คBฬกšุฅธ/[๕s.-bH)ษบz๏ '}ถืถ๎wœ!rมXฮZŠต ด.:’Vnื;๎-๎>Š:แ 6เrฝ‡Uมcsี4kฌVWฌ{’๚‰๐#ญ˜5฿‘0มBใ์ว`0ušั".Qสฦฌ›dBด˜0ฃ”๕˜Cžะr๕]ํ๏เ#ํบQ9lqœเN^๔ึณฏ้๔h~ NU\ด ผ16š ~้่“แ”S‘n‡T๒lข‘\THาฒฺ›-ูษ~ชG~)$…oQ7-๋ฏC๏ฐฬศ์}q%/a™ฆณvOฐช|[q4–‚Š’ณ~Bc-$N7ž6ฝŸwา{œV้คƒ.&ฃ๖(†oนฉ*๕n<งุn9กมJ– "a‡ฉ ะ”อ+ก† aส/ปบบ๐;7zDุZฮท{ืtM Mp—ฃ iุškผNPwˆุ‘อบ๐H`T ๖$23–†fำ๖ภะฺ0šzฦศ;ญกฆž็"๐]Œ๒ช‚*ŽYฒค้,ไQกWุเ๔ธlS้ตฦลOŽrW$5]KกVูปBโ…š…IธŠkฺ|฿=ฒ&ม[ิ่๊ัล58EำRค0‹GkซsS๎๑มnใ๒nnuใ๚๑ExKะr๔ขฌŒ}‡~m๑ู`ŽG4u{ะั๑=]6fค๗โืจ Boึ&< ™รฑc;2 ฎP$รวƒ{mW_cž๕ยช'B6ะŠ?$ฝ^z[C—Yนญ™้ฅjะN๓~ ฆฎ0พป›๚tขฏ๚ฐโ„6/)-‰1:p$D๊ฅ…ศ— ฎ ,'ณ†yเ๕ฑ๘มv˜ฤ …nœ‹FณTืั™['aฆMbฮJ]ป%&รฎฌlc6&ย่IpFๅฤ จo๐Œiƒชฤษใ5ฑ ๘'rจไหๅr‘(qผ๏่๚ฎ๋๎พz6มฐ๖ž๎ษ(5๓ะแE๔เษขีŸl\…L๑kบ7ซฐ1ลY4^)bู—ยฆ8๙ผy๘ฦชไ Nžุ=ƒ›9zT–^[T$‡dkยœ QโiK%แˆ6Žตqต้็๕ฬ๎fO|เฺเcะ8$•ji^vr.QQR"โYแrฤŠใโ๕ธ จรk๘’ฅrŸบผKˆขต๛ ๆ่ NทeํฯRi๛4ฆพี!3R๙ขบ"4ฆˆศ๑๚šn™bแmษ-y[Xฆฆฯ๑."ฦ๔!”๘QKฏๅE\N๋ต4gี ื๘งฟฆƒ๏ฑaNฆp‚ >k)9มม0ˆBZ๊ฬBs ๖ฅyŠrŽšerซ)vฏขฎD๓๋t่rv\๛v๙[ญ>๓rตJmœ– aœ๒ซตฬผ›~u๊๊ีบ>ตrMZœฐ˜cํB<๘ใ`)\yืt|๏r'<†๔˜เ>๙ืึใ[ๆรŽ—๏ญ่h7๋๚ญZลŒ8caI!น ดpโขŸอฬฎ,ถGรํ ปk 5@ศู๔๗`๔ษiw ˆาnะž8pŸvฟ ็ษ้*ฒย'O ิŠฒ A[ศ.กคrh๓T pR?+;ห๓\*H‹sLqๆ๋Ušนf–โ:ql-ฤ‡ค ๑*6!…h็+หฌ{hท‰๖- jgฑkษMMษืP#ใถไ:ผ}‘ฑธ{/๋๎Vห฿ลถC]์˜™๊ท&[ณW$ฺซ^฿#เ๛ธถ4fWa\ ‹ม5์๐่บบM[6žฝ)Tง3—•›~ฺฮญษิภ ใ:. Z”ฆผจ`si(อR๔ฉQ…ผฒฬ|/๚`๙ ilฯ^ฐL# ง๗๒fค-๑ื;-C;_๙*๙{@EMCooร‚_คœฦร๕7พT๖ใrqzFต%ืฏ|™šUEฦซUs^œv{ ๋คฆfQ<ฤก‰VP๏รสTfอฆ๎?ฅ๕mุpูP*ศ&ฆ๗ะQ‡G‰๙{cœJ๏๑EPe2)ŽxPฝ0Aั฿อ๛MษชZHถj•"ืป"ูAฌะC+zq‰mVzแ–ž๓U%ุCต:@1ๆใ๐WนŠ ๎[y)ฮJ@๗oฑb% ๗j”A>)Nาว€ิi็ผ$’A๓ˆภ์t`>ฬ?f0gH36p่6แฬD|๛M›ไ แ๖4Nกฐ œ’ 4JฝJฺƒฏ ตjคž˜ฦ‡ด๓\ ํp 3ำ๘Žฃ8ภฆชัใ–ะฏ๏”ปญ6pฎ๙V?:ฌ$‚sD๙ภN๚ตฦน‘2๏’n’,ถ„HO\‚[ธ๘ีธ›๖Kู-)ซW~นi้m™?ฎƒTะ:ฐฬ๐บUึeYŒำใŠ-#dJe)ฏฑฺฯZชี5”?๘$ปๆก\dฉW<ทนน,ษ††š;ฏุทธร5ออ๒Sธีธฏผ—TซT–๑šฮฬ„f(PYฐv=Q ~DX*ผ฿8๘่พฉs- ยหจฎฮ€55 ญ XขRฅl QCฯแคฮ แ“๘ัภl|๒5ฮ{๛ำฆT\t๊ผ•+้๏ฃeปnลธ‹’ยPsำ่l๛3™๙UOฉ[๎วZปษS3ๅภ๎Ÿ*่์,˜ช†฿:ร›ZพฦิL‰›ฌ†ีS๔'ฬตๆใ*๕๒Œ*@จ๘ฤฑ~xgno2ฑนแˆโ- …Œ ณWณŠซฝV;ไpZๅ9?~„ซ$ซ6Ÿ<”ตQr‚bQ8&๓se•ษEbฏฺQ,ท^|Bฒต๏็ข˜๏VdฌV-ถ(]ใ .๏่หŽ8/qhVกnRฎ๓ฎQศD‰*๙U(*1h็1ร`ุQL{…Uj `เา"šo3ปๆ™Vจl…ต: ัํ๊ยุภ jaFaเžEญถฬžZˆ‹g1ฑทzฐฬ2ึ ี:อAuูZIf6–ƒ2ีtw+Š‰‰fงD‹๏ษ่ชฉCL-}gปตZณ0>า„๓xJขล>\๎ม๋Q‰ธAฺ_Cถi‚h฿าbl] ‡6ี4*šAหฏษฐqX‰ค7„ปY๒X.€-พŠฺีธ•aษ‡รVโhฌ–iKg•ิฯqNณRฤ†N(r'ม]ผเ%ู˜„…ญ๔ˆ@3ิอ€ฮยjงZฌ๗J†.;ใnm๚ฏีฮ,S๛๛ุ0xฏณร๕๘ุอปฃOF33ญางถ…ซ<$'ปธG—E+ดฺ}ฅฅฮ๓๚๓ง…'1๐f3›ฦาyะ5/&คZœ\RB๔7dmํ๔]ๆผ8งงย\อ„3฿‚„ศช˜@๗์oฟ๛T้ฟ3eu^ทW@Šช”–Žฆe7l–!Bใ,ภs‚ไๆ1อ฿$šไ๒ฏZงใ&ู’็?ฏโdC้แ (YะฆSm>‚J"&ptˆชPใ‡„BFฌ๘ดฺฑเู๘4ŒGแ5œ t^ฤ†$ฏ๒j-aใ g^มสCค–…ƒAsึT=kกTS,|€rเๅš9IทฝBฯ˜ะฌ†๖'ย์vGAถฮ@อtภฬhQีNj†&€ๅT=ฮxt;2]ฏPก|T- Lรžƒ’ษ็ฟ๑e1ใฝWรZลš*MrH5?„๑‚=เัoฐฌ"๋ฦ9ัK5ฮ๘=ซ'kษ-*•„่AฆE| qา”‘_?\ฃ7%๖Ž|M6ฐfช+““+’S*}็W_]3ชจ๚ถfmฎุาหณาm w!–—๎๗ๅ.R#‰้ฌช;ฦํ๚ฆฑq q๓71Šึไ$ย•™ีฏ_ภ๓iKเ&์JฉฮฌMฎŠฌ๖วemๅVฤ5Pฯ0>พพ Q็ต5ช†W•ฉHญIhืๅ๙&๙4าIlำE7}โsศ้ฤm[cศพ์ข„|d^ ข์๗%Uv้1ฆD“ฒ>“.่Tฌา7*้=tƒZธ_งใŸพ1ะฅ:=0pZ๛š6า‹ŠN„t(ๅuํฦญ; วBา]ณ‘$€kกฺŒ€ย.๓{ชFฤ*/UZ’ฦN็ ฆ|oqŠสK—G;^ึไพž9Nง๛eํxK‹ ’กจ\‡wh๘๑~ก๒๒ZpHิb‘‹อไ‰ธ ปˆ[kถ8โษอฬk๗โ.bX.QพXpธxYa^ตะ"˜ั#๋Ÿ™ใBณwnbคฟ๓ๅuำ๐ ้m5žFฒฝ~>วะ8€ญมb๚ŽไN:p4 ต[gv^ BฝำFะUz๛)?œผ60ะFษษ8˜‚ย/2 ŒC8จŒฎ>N8G๎อ%l๓%่5บFH˜{4„6hงŽ๏ธ4Ž%ฦ# 7ธ่›๑Žอ๚หx ๓oบN tช\•'฿ศจ จ ๚ๆEฃŽๆฝ0#ƒ๏jธNรฃV๏ำนdเ?WlcWื๐‹ ฤํ ลพฝึตตu๛-“ป}2„2จถฅENฟ}#‡ไตต2H^a3๐๕ปฅrปมqsงคฐˆ„-S3&ศ๏๔„fดํ‡ฃฬ๋ฮfwl.š=W๘8ๅ„,๕เาcHแฎjžcT๊ฑWฎ‘ื s9๒0ž๎”ZเDฮM“๚พC2’ZM’๛djตŒtŸ"8ภ:gํ{.ฦฑฐะ1Fb6ƒ1ว8"yิฆ>˜๕ฟ ’ญWภ9ฃ๋ ำVšŠŠ๖ ฏ`ืjฟฎšฟT๒””‘‘€ฒตr,nƒฉiภญม ฅdณ… ๔้qNงช .g+ ๏Sย๋ผ Q๚๙ทพ๋ แKaBพŒเ?_ํ‹QE ‡“ตrธ†jค๎‚h>๔•Eฆำ›;Cืญ7…‹ท^qฏ ฦ`Uฏe๚#-Ÿ˜ท;oJ˜ฤ‹ใาๆŽิ>) ว‚;Jgํฃฯืญ9R;OgํขลiI7๚}—โ8Kก’œqฆjนัeุ“ฃ+ู—'n๑ฯทk3‹มญปeFฯ๛ž์…0ฑš๒ฏ฿V#ฉํฦpฅMAzb^PŽ๗Vฯuค~1uบ—า“•wnศ ^›.II—กŠ_ฦฬฺvdWฎศ๖๓ฮ[Q,อ่+Lพb‚ํพษฅๅ„ฦฤ‡qผ 9ซV}๏ ”ฮVัw4qUไ3&jฤฑHYbผ นˆ ฟttTœ๕ˆ7ฯ๒’ซูarBwP9?)u•้‹T/ูaฃ•ŽA19–ฑkชM \ำไP฿s›<ุTažะใ@‚…๕qฑุ+๛ฃ=ู[5ฤอ”ทถึื?ฒ9ฃWภษ+^oฏ^Eเใ8s)ๅf็ —2a๔ๆญQŸxคทi‡้& NE>"^NaไaŸ;fŠูฬ9]NE& t^ฐภCLz'โe…8ZR๑s&6ฒ7_รฃcyJž‘1 ฤ@TZฐ?SD2๛ ‡|ิP๕ิหOำŒ้\dชR๏๐๛7zHี๘ƒ๎ฑ9iศ‹Q#ตฟถ‚zrณ๓c.๒4๖†GR4ฮศqxฆ๐พ<2~X’hต๗nแใเฉณจล2ˆauBญNCั+›ขk—X๓0ั aj5n>‰จ๕ฒe3๖vงำ๔้<ฅ>ฐ_ฒŸ  uH: XR%~9แ!4๖oััผฆ่3…บโ”8?ถ‚ ฮ1d#๏ิั–šA&‹„„{A!i6 ‹Œํ/XaŸฃแใ‡ค=W‰;|๏๐ไ) ะgพ~ฃ ?*พๆ‚ฝร }ฟใฺงˆKtฬ>5|ญEŽตะ๎ัี.ง๔A๛š Q๑6๚ ฒ€พ(6 6ั”ฦ7–ฺ๗<9๙๙_๐•Cม f1๋ะŽ้i8พฎ†ๅป, Vป4$ภŸutษ๘œ๘ฃฦiม,.`v6r โฃ๒ŽP ฏฝgFBษŽว t๒ร็ฺ C3ฝ;˜ ,ผoร‚“ๆใœx| /K๓Mpฉ1S_พ‘X.f๗Vช†#ผU>ศ’ศ๕ˆ#B๖๑]ต A‘IVoภฬะ†ฯตภ‘ฟGTV1nr+ฃฮOXยS•% ‹›ยณถ™fงOZ[_9œ๛‘Pญ฿ฐ๗ {Gln‘%฿#ฺhภdwฟH ๙=† ธyฉe/ํชWผณดถ>า,ำ๖ฌฐIP,*MV•๐„~ยบK&ใeขฤ‹ป๛M์ฝฃ=๒)‹qFฟ Sถ฿ด"ษGั๋TšF™*นLX,hŠ[—ดบ“งwŽwดํe๑WQEูxฺ๋บ ?แ่“{^ฺ†EŽx๏hปiปขื‚„J”ณรภHฅ|๓^ูอ“Š…ษe*^‰ะฏ.ใuฺxE™่๋หb#๎;›’๔ิ<]z]\ํƒืจwฺNปhoผchqธE˜ฃ=บ็โ4Q1ธ7็Wฝฬ“lร•6แงฟญHE_ฬฃ ๙qy‰แ YRŽคซไ9~l4sๆVy๙ญ™`ŠืU฿›,๙Ÿ๑ืล#_สuด๙+De๊๊ํ™เ้Mูข~hณqช๋ฒ‡’ท#Y๚สๆ๑z$; 5อฏ9$ตห zฒ> พ*j๕OŸ๘Œ๑$œะ$O/ฏภรxRํฝƒยtf-}*žo๏ษฆ๘ูฬ|3†M;xจฏU”•ตl/.๑~XวŽฏYแผ4™x3&ๆ๋ืxฎ";ค$KI๖’5dฺญ ฝสแ๚‹~w[สิM9Oุใ%4าแQจ}้S^ž์t–่@ัหๆw[›Y;-๖ภบs;ขbพอwH-* ๏––รimฝถIส-ถ1e/•~๘จTNNฎ.๒pฒ฿๐)H$๛๋ซW๏฿~ฮ๐ตษฦฆํผO ( ้9่†,ู ]gM6r๊+„#ป%ƒเ/swฏAœ$๕Œqฦ4หO> d9}๗+๐ั$Œs๔šณ?0ฃ™a,>y‹นฺˆs<๐=†,๐‹c_*\โƒDํผ}ศ2MํฐอฬT8/ํ4ๆgๆ'ฺฆžโ8'๛}"‹Cโ*„\9ฝ#Y>z$Žๆ๖ิ7c[s“|"$}ป ymฬ๏ีภซzQx 5ท%ๅ o๙ๅ“$j†kะฮp)๑xฝฤ๛-:บ†ะ˜|?˜oใ๘fง‡gFrภž฿2ŠS‘วZ๗Œq}qˆŠต …o€,wyลOกgสCF1ึl˜็Ÿ'ํL5T3๕๒3๛ั๐yชฆM‰9ฺ2"s”˜๒5uD•‹6ภิ-J์‰U†bsบฯ ซO)ƒฬwธR -2ๆ/5f<ชBQฬ4kŒ˜๊ญGน )%฿ผใr’ฯ›f@=šโBF™‹๏CBฑฉ‰‰ƒ‡&'‡F}@ฉ&ำศ„yืubโะมษษC?'ซ๊ๅ๑็Sๆ49+—ร“ไฑC๘ฎณํIรฎ๐คูŒ+ื็˜๋f/RซU๊ ฬ๎C ฐFu:C*ฃ} †T:บถ}{ยฝโฒทิuชฮื๘•ฌe[!ฎ–>๚? ‹ฤๅฺธธ"วM 8gzƒ๔0\HkƒิZณ:ฤhๅญ~™@ช+๔#ซN๊ ึfjพ็yต๎ๅ‹ioภ!„B๗ ๑๔ท“ฑR'ฉ5>ฺ`ฺ๒[!ยฤTˆ`mCš I…ัร}ธn >W฿๔!M}Uหav๕ถ4€3)!งkcศ‚ฒ๓m?’Ž ๅd๏w•ใvซ!ื”;Xฯกจ}ฝ8ํvt•๋ะ"ำผ# kยvXŠJ™ฑ[“lถ[Z™Mร€ง๑รXC3l๊–[ รTaผVj‹ก…ฺสปฅๅŒัฌ"ล“ั๒ t:‚(ž๊เฆศม<พcZ๓ve—Q›ว๎T…Ÿ qHแ‘i{ า้Š€QๅŸ“'ม“ร–ป“i ๖Pุ๖๏ฟญ่ศฏแํmKสA็IŠ๕คBFƒ ฃ=‰‰ตโึTแ…ฝถเ(โœ&TSŸ?/๏ุA:ึทฌปะžVง(๘๗@w๏Fa^ใฆ]…Œตไo]*ำ๑99ฌRฟiแ๑_ิ๒˜ภ™ฮห2vM“€ุ`Pง“๔fฆด{QYฯซํH#V7vล7ฬาฐ q>@เ๓ซ~uษ˜ื†Axฐร/ƒซxูฐBเ3ฃฤ ู๔‰tƒสมy†b0ŒžnG`ม ๒EฺD์ูไA’ิ: ฦPุwIำ7ฯ์nWำ๗2ED}.(h•"‚๓ใU]ข9Iํh_๎Vฏ@‡›GZ0C pb ๓:ใ™L 3กtN*ชN ฝ2ฟำ!ผ3Œ Caณ—yn.•สษ‹Wโ`ฬณ}ฑQBฬCชรi มก8*ร๏{57‰น์O#aTขหBฝŸ‚ฝUํoiง0็ ๓_ห๐ั๙^ ChrU}~rศL 1๒z>..๖=%Gมš›GŒฃ ๋o ™‡ ŒE้u‘PณPsุ˜ธ่๓8ญ๐PจŸu&;ๆส*Œ๋๙|i&คPbศ›ฐฒ˜hา;ด[—€|y*cƒVšh†าผา(”~ฮ_A•qU2ทฝ๔GIQว3`ฎ^สv=๎@ฌK'คตะ‡่หZ#4sJ=บค:sYฉ่ sฺฅbยyj ฏ๋ทS_Eƒ"Žช‹Š@ž~‚Ÿ็น>ญ86บ๑#ฮyฑฺไๅ[๏่c๒S๏„ฅูลฌ–”๒๒#ญSJ”GZ๛ฎyvv฿S๙ัๆ‰pธwaTšžฯ๕ล/, 9'Jkv%%.†~o๒[ฬ๓Ÿ ่กงœยRฑBjขŸS่ศ€*$'๘่…ฉpรงSฅu เ+็9\ฌํ_f+ๅ๒๙8๕u\,ถสำtๆๅ๐pžัะkุฎJ0h๓(]N„Qศv๓ณWฌวฬ7๓ •8ซƒ6:ึฃฯโ ท๑Wc“ฏY_i>๚ฌŒ"‘฿Rก็๕’โ(ำe]6๘นชRA%U—6&ดF]”ฝ7@ฬณk3X h?ŒมK๏เฎQค2™Bkพ[?.€ร.KกศKAbŽฬ6ฤ5ฟทk๒’eŽ+]ฒFŽฐe๖WHฉU๒ซ๛0Oืงพ5…ด๘ ค๚๎ไe3H‡ฎcoว>l]0ถ2ฑˆcน‡Hส9š{Z {sO๕–!ฅA,7†?ลท3wไฟŽA เFjฺธ8ธBํ&8U$G…กยตู฿้$ตY5˜‚†FโL…5nว์ฒุ๒1–”> q๓บ2ฃฃ.ซ6“e—้ ๎ ์ผฺ๕—œ+˜–@/ฌข๐™ตkํb{เ๗(ล7ะi=ฅษ{lอฌ‚ญโฮๆฟฆ๏ 8ซ…1g(ป่%ฮำh/๋EfถMนาtŸ5ฝŸฬผvgฝoฺ ๊~เฉ›WKi็ˆถUซ฿ุฃ–ญwฌRS‚E์Fํ฿Tฌฒ%ช `=ํ‹๘|*=1‚๒*Ž๙ปทSงX๖ฌ€ทุ^๊๒ำw)l–รึfQHใ๚Œ(YS๔„SหŒKทๆ1ุโํW]ตf™Ÿทฐ7ืšณ^&p๔@T'.์ภ%3ณ•„ร šฏด฿๒ป๙5ฯzaTf6ั๋ฉA5L•๓Xฬกๆทม|๑Lธ-Ÿๅ–ฮท๊ฤTืg{A)ช๎ญฎFั•."h“๙jข A;.๐โ~ย …o%ฐ่ G#ั}&]ืพc`CปhH9xnN†ทY ฮl๐ฒcค\+v\EŸฅซฦง1ฆD9KทXแ)2b.ฟ๓ญ“N๔ฺWขงŽQืฉ$ฅ/ฃ”|6tรฐ™ร32ิ›ฃด7กกฆ–2ธัะธyu0eื๑€)’Nุuh'dœถร๎„๕~xY‰ษ ‘ด>ฤฺ# bˆ"k3ำ๐ย ผƒฎฬ: 9ฟบขvˆ๚$ะŸC:ส)Hนห> ีฆšzว;e“d\jmfŸไOฯa%๒9š‘ˆcK›xำะฅล!kฉ%HDn“{Y"“{n_ึ} ฿)9ƒ= _/๛‡Zฮ(้๙>l๚ถญ๗YžŸึV๗‹gQ#ง฿ญ:Qฤ•ศbwƒว๗ี$พzwฑ๐ูฎ‹#ฟปใU˜?|ล๐๏Gิฮ„hมz{๙oบ$w๑ืฯœดบ)|Vh‰ฺ?ปŒ ZV7พ%ŸG๛o/ฃื†‡๔้Eฯ"นKำฒ… ์ตlฅp76ฮ-z !มl€4n>”ผ$\แืzV?sz๛qej์Q็๋้]m‹›ๆ^ๆ=^ต ไงํญฅ! ฒบl…๔HB4sL i9}2ข^ืKะ5ลOB๚)่ํO ญ็v^~ช๊ต๏€xฆ“rm\Kฺ&G^ะ5C็ะLผ}&Fบ๒”ภหโจB]K†กn3††|อsGjy๐kOฏตฺbๅsฝๆaW?R6๐กทฒพJยวfhใฺ2 ฤสlBSง\=ขjีีV—ส*”๔Yฆ“^ขขม™„หบ^E)ี่*”\ฝŸ  ย‚rิr(aข@๘„6nิŒฃ?ฅ}ฺdLฐฉฆšgขIvqุNcะวa‘ฦฎkŒอ๗mLŒ๖cมA!คฑhdธฃฑV๋๐ตนฮ๒wc=ขฆๆ†–แๆ–ีs_า:Šา—•ขsLหํฃg>‘œ—1*4-%๐&ห๕0Ubƒ)Eฌ†*bธิึ51—ฤ๏„็ ไ+่+;˜Ž<…ซ†’`!qฐfอฮM๙*ฦ,[/GK+{ร—ฎ,>CโLŒ๓R%%cŒณยฦร~‘’'EG†A‰ฎGบย=‡hยไŸ”Žฐ8:ID์N)ลWฬปแAF)ucw'qhอXรจฒL@a„พ~Œ6ฬPc2Lใ"ฅA…2b์ศU ™ภ&Œ€‹ ฏ9๖A#ำQLOฌ:E€9kงฉ’‘ฦfKF•b93tบL$c‰หฌpLz ฟ5ิdฺpข๐๙ฐ>$`œ.๗๏ซ~X๓=ถกใ?„ Nอฐ/ŠฤฉLฎPชิญNo0šฬซอ๎pบฏฯ ‚b8AR4รrผ Jฒขjฟ}๘Ÿะ ำฒื๓ƒ0Š“4ห‹ฒช›ถ๋‡qš—u๓บŸ๗๛AFP 'HŠfXŽDIVTM7Lหv\ฯย(Nา,/สชnฺฎฦi^ึm?ฮ๋~๏๗‡ร ’ข–ใQ’Uำ ำฒื๓ƒ0Š“4ห‹ฒช›ถ๋‡qš—u๓บŸ๗๗b$œซtV&gฎฯ–ํมr>ฟ<ูy๓ๅ?’“˜๛f{็ดทฃ‰ต›ž%๎ต๏วฬ~฿Z๛aลzWพฟผ๗๕2Ÿตsv๏ภฬ๒™ูeWซ‰ต‘เ@DDDD$""""bffffึ}X ำO„0ฦcDDDDDฌตึZ›6W๒08Bึ็Iฅƒ.HฌW ข฿ˆวะ9 ‰u„*ฅ”R*J^}€ฤ:M”ด$I’$IาF‚‹™™™™yัŸž๛๓ภ_Wอฦ ZoneMinder-1.32.2/web/fonts/MaterialIcons-Regular.eot0000644000000000000000000042763213365153155021144 0ustar rootrootš/ิ.LP‘bฺMaterial IconsRegularVersion 1.011 Material IconsFFTMx/ำ .ธGDEFSล $GPOSเ๏œ.€6GSUBไาฉQล0iPOS/2 u"gˆ`cmap๑๐1เ Œcvt Dgaspลglyfmฦ0u๘head‹c 6hhea4D$hmtxo5f๗่ขlocaFY”œmaxp'แh name‘ๆA( postXณใฒศ5:ะฺb‘_<๕ าฃสาฃส.อฐ@.LfGLf๕„€0 +++@+@@U@U5+@@Ukk@+++++€++ซ++++@+@@UU€€+€+++@+k•@@+@"UUUUUU+++U++,@++@+@Uw@kU+@++@U++k@@+k+++3@+[kk++@U+>+@Uk@++@+k++@@+@kU=@+++!k++@@@@+@k++@@+++++++++•••••k@U@+U@+++@kU++3+ +f+•‡@@@@@@@@@@@@@@@•+U@@€ 5+Uk@@kUk€@+U+@+@w+@kU@UUUUWUK+U5+k++kk+++++€+@@+€ซท€@€++k@Ukk@@@UkkU@+U@+++@k€+@55+k€@+++kk+@@+@@@+@@@@@@@@kU+@@+U+U+จ@@+,1@+•@@U+@*@+@@@+@@@@@@+++€k+ซท@++U+@U+++@WU@+++@+Y@%++@@@+L++@@+U@%UUU€+@++++@+@+@@U@U@@U+€@@@+++:+@@@`kk+:@@U>kk@k@@U@+Uk+UU•+•U+Iซทk€€kk@UีVžžUkUU€wk@+++@@@@++@@@@@UU++U=@k+@+* +@@+U+€UU@U+ี++U++@+@++UUU+U@+UUU@+@U+++++@@+++++@+@@+++#+++@@@@@@@UUkkU++U++@kU@I @@++++++UU@@k+++++€€U++U@@+@@UUU++U@@+++@kU@ @+++@+k+@-@k€k@UZk++4+@U+@k+U@@++@+kk@+U+U+UUUUU++++++k++@@@+5'€k@+Z+>kek+@++@@U+ซ@U@+@+Uk+?+@++UU+U+++++, , ๐x@89_zเเเ!เ$เ,เ1เ9เSเqเผเฟเฤเๅแmแ•แœแรแศแะแแโโdโฤโษโฬใใใ8ใเใ๎ไไ.ไ<ๅ6ๅrๅๆ ๆEๆลๆๆ฿ๆแ็้่่่่:่Q่s่ถ่ฮ้้้้+๋L0_aเเเเ#เ(เ.เ3เ;เUเฏเพเรเฦแEแแœแฃแศแอแุแเโ&โผโฦโฬใใ ใใใโใ๑ไไ0ๅ-ๅ9ๅรๆๆ#ๆฤๆๆ฿ๆแ็้็๎่ ่่4่M่S่u่ธ่ะ้้้๋;ำฎญ (     อฬษศiGA;73,(ๅŽ‹QPO๋๊่็ๆ๖๔คtr๔ิะวลง•”“’‘Ž เ<09__ azเเ(เเ,เเ!-เ#เ$4เ(เ,6เ.เ1;เ3เ9?เ;เSFเUเq_เฏเผ|เพเฟŠเรเฤŒเฦเๅŽแEแmฎแแ•ืแœแœแฃแรแศแศแอแะแุแแเแโโ&โd โผโฤJโฦโษSโฬโฬWใใXใ ใZใใ8iใใเˆใโใ๎ฬใ๑ไูไไ.ไ0ไ<ๅ-ๅ6#ๅ9ๅr-ๅรๅgๆๆ ‚ๆ#ๆE•ๆฤๆลธๆๆบๆ฿ๆ฿ปๆแๆแผ็้็้ฝ็๎่พ่ ่า่่ึ่4่:่M่Qโ่S่s็่u่ถ่ธ่ฮJ่ะ้a้้’้้—้้+Ÿ๋;๋Lบฬ  !"#$%&'D***2:BJRZbjrz‚Š’šขชฒบยสาฺโ๊๒๚ "*2:BJRp˜ฐ่Zฎฦ๐:Œา.jจะB~ฐฺ๎ 4@Xz ุ:z˜ภโ.ftฆ๔ " D l ‚ ’ ะ ๘ & b  ผ , เ B ฒ h ฬT ภ๒(\Šฎ่^‚ฤๆ 2TtขฦHzŽชภิ๒8fค๒hŽบิ L†ฎ๖>jฬXžึ๚@b’ผ๔TœึP~ธไPfŽขฬ์Jzคพ๎@zšผึ๊`Šพไ &4Ln‚ ฤ๐ d v Ž ฌ ศ ๖!*!\!!ส!๚"("\""ธ"๒# #F#r#š#บ#ฺ$$>$r$ž$ุ%%:%b%†%ถ%๔&$&d& &า''f'˜'พ'ฬ'โ(&(n(Œ(ิ))|)ย)์***8*R*v*Š*ค*ด++(+R+Ž+ฬ,,T,Ž,ด--–-บ..t.//t/ช00x0ž0ฤ0๊11>1b1œ1ย1เ2 242J2r2ฎ2๊3323N3l3Ž3ฐ3ะ3่44B4b4ž4ฮ55&5B5^5~5”5ฆ5๖6606H6t6ฌ6พ7747`7r7Ž7ฬ78828^8ˆ8ฒ8๖909Z9r9ˆ9ฆ9ฬ::,:f:ฌ:า:;(;n;–;ผ;โ< >2>~> >ศ>๖?&?f?ฒ?@@>@`@ @๊AA8AfAŽAถAฺBBBlBฌBไCCPŽP่QQNQŽQภQิRRfRศS S.SLS^SจT TjTศUU^UœUฤU์VbV†VภV๖W WHWšWฌWๆX X4XfX XฤX๘Y&YjY’YฌYสZZZ$ZrZ–ZดZ่[[N[r[ž[ิ\\b\œ\ศ\]6]p]š]ฬ^ ^:^j^ฆ^ึ__€``2`~`ฤ`๒a(anaˆaพaเbb^bŽbฒccxc cึddldฎd๐e*eZe€eฌffPf”fไg$gTg”gศg๎hh0hbh„hฬii$iLiผi์jjpjผjk(kLk’kฒk์l,lVlvlžlสl๐m*mPm€mฎmไn"njn nฦooo@oho–oฤo๚pp2p–pฬp๘q*q~„~ย~2`ˆจๆ€€J€€ศ8Phˆฒ‚‚‚v‚ฬƒƒJƒƒฬ„„2„f„ค„โ……P…–…ฮ†*†F†p†ค†ย†๎‡,‡H‡‚‡ด‡๖ˆ.ˆpˆผˆ๖‰‰@‰^‰†‰ž‰ภ‰่ŠVŠxŠžŠึ‹‹0‹‚‹ถ‹๐ŒBŒxŒไ >jช๐Ž*ŽdŽœŽิ.RjŠุ2Fb”ธิ‘‘>‘j‘ฐ‘ย‘โ’’F’j’ช’ๆ““L“r“ฆ“”:”„”œ”ด”่••L•z•Ž•ด•ส––"–J–x–––ฐ–า—6—b—Œ—ฦ˜˜D˜†˜ช˜ุ™™2™`™š™ฤššBšถšไ››L›€›ž›ศ›ๆœœ>œfœ’œิ4Lzขส๔žVžฮžŸ2ŸdŸฆŸา  n ฮ ๘กPกŠกศกข>ขzขคขฃฃTฃฃดฃึฃ๚คคNคhค‚คฆคึคฅbฅขฅะฅฆDฆnฆ ฆฮงงTงlง~ง–งฎงฮง๎จจ8จRจlจ”จดจฉฉ$ฉ8ฉ|ฉจฉช\ชŠชสช๖ซ(ซ<ซvซฒซาฌ ฌ:ฌvฌ ฌศฌ๒ญญBญdญ†ญถญ๚ฎยฎๆฏ"ฏLฏœฏฐ(ฐBฐ|ฐฎฐฑฑ>ฑ„ฑคฒฒ8ฒnฒผฒณFณxณ’ณะด ด„ดฬตต>ตrตดตถ:ถlถจถทxทผท๎ธธ@ธฐธไน–นผบบ`บŒบ™U.ฑ/<ฒํ2ฑ<ฒํ2ฑ/<ฒํ2ฒ<ฒํ233'3#ˆwffUซ31111111111111111111111111111111111111++ีี%5#5#2"&4***Cฐ}}ฐ}๋€€V++@}ฐ}}ฐ++ีี6264&"2"&473#3#บŒeeŒeSฐ}}ฐ}ภ****UeŒeeŒ}ฐ}}ฐ€++@๋ี %5#5#***ึ๋๋ีVVU++@•k@ภี %%5#5##335!57546754623"&U@*@@*~-€-@11@ฝT"๊+@@+@@Q--|3Q  Q3ี++ีี2"&4264&"62"&4๗  P88P8ฐ}}ฐ}  i8P88P}ฐ}}ฐ@@ภภ*26462"6"&462'32"&5475'2654&'#462"€  ๔  ‰Ppp pM‘t!W|WJ6*  ๗    ซp ppP`9‘s)5>WW>8T)฿  @Uภซ/?54&+";26=##53#54&+";26=##5372#!"&5463€ @ @ ++u @ @ ++สึ V  @  V  @ –UUซซ 3#!5333UVVV*V@๋ซซVช@@ภภ 5#35#535#572#!"&5463@€€UUUชึ@+ึ++*+€ึ*U€ห€#ถถภถ€€€€5€ซ€7#'7๕ถภถถ€€€++ีี 3#'7##5%'53`uu@`@@€ @@u@@€@€@`@@u@@€ @@u@+ภๆ5462"'&472653#"'&'&'&'&5462#4&"๕ ,,r88EEๆ+2#))W~V+=Z>$( *,,ฝ8 8EยEo#27%)*?VV?->>- "& @Uภซ#3%5354&+";353265##5#35372#!"&54635+ @   • + +สึเ@@ V V €55€++๋Uซ๋%55"&5472'654&#'7UUFeK5FeK5UU€@UV@eF2)!5K+eF2)!5K@UVk@•ี3#5.5326"&=462q$K5*5K$C\CW4&&4&6QFFQ6/==&€&&€k@•ี(3#5.5326'326574&""&=462q$K5*5K$C\C‹ 44&&4&6QFFQ6/==ฑ„  „ อ&€&&€@@ภี!''#5.53327'#"&=''5462'65[eY!*5K$C.#&€€&4&U ภ›YFFQ6/= #&€“&&€%!+Uีซ3#!"&546;3'33'33€Uช*@**+@+++@ซีVVVVV++ีี $5#5##33572#!"&5463!!"&5•U+UU+kV+ี+UU+UUภUี*+++ีี $5#5#75#72#!"&5463!!"&5•ี€€ีี๋V+ีk**ซ++U++ภUี*+++ีี&!!"&55#&#"26=72#!"&5463U+ีUU ,k€ี*+*u ,vj!๋฿%5#5#%'/7'7'?7***4M(II(M44M(II(M๋€€V++k;ODCO;4&'537'#+ATTA/;;/55๋UkkUปiˆi,NfNSxฌkชk+Uีซ %5#'5#5#2#!"&5463ซV๋๋๋Vช€ภภkUUkUU+@@ภภ )%53'326=4&#5##5#3532#!"&54635+KV € + +สึเ@@`€ V €€55€++ึ*"1าฯ %3#57#533/3#'##3#7P‚ถ~}ฑœS*#`'m'นc23e2จ"ท"‚o.๔44#2l2+๋ภ2+53!3#"&54637ภUU€UU@€€ภ**k€€U@ซ๋4;O%32?6=&4&#"7#"#"'.'&=4?632632#557346355"2653"&  (     Y&”dGkk4LLhL+eŒeผ++      5F FdVkkVKjKK5GddU@ซ๋'G‰746355"2653"&732?6=&4&#"7#"#"'&'&=4?632632#2=&4+"#4632632#"#"#"'.'&53;62=&4+5UdGkk4LLhL+eŒeว  *  q        ๋FdVkkVKjKK5Gdd++         U@ซ๋2F7#73#25465;2#"'.'&5332?6=/&#'46355"2653"&๚ 3%    ฉdGkk4LLhL+eŒe/     FdVkkVKjKK5GddU@ซ๋4;O%32?6=&4&#"7#"#"'.'&=4?632632#557372"&53264&#'7  *     [&GdeŒe+LhLL4kkผ++      5F dFGddG5KKjKVkkU@ซ๋5w‹%32?6=4&54&#"7#"#"'&'&=4?632632#2=&4+"#4632632#"#"#"'.'&53;62=&4+572"&53264&#'7 (  q        3GdeŒe+LhLL4kkผ++         ตdFGddG5KKjKVkkU@ซ๋4H7#73#25465;2#"'.'&5332?6=/4"&53264&#'73%    GdeŒe+LhLL4kk/    ทdFGddG5KKjKVkk@๋ภ ###5#5353!2+#5#"&5463U@*@@*ซ€€kชk++@@+@@–+++@๋ภ .2654&+3533'67#'#'54&+326%2#!"&54633#%3#ภK  ณ&  %B KK € ++++ €++- 6€II€ @€๎ึ* @@+Uีซ'7%5##5##5#;26'5#35#535#55#'#35%2#!"&5463ต U •UU555667ชีk`KK`k Y€e€KK€KK๋+kี• %%5!7!5!5kjjภU–j–๋@@U++ี**U+++•ีk 7''7+"&=46;2535#5#53เ0%%ต€€+ชชชชชภ@0 0€€€ผ++ึ++€*UUซซ6462"UeŒeeŒบŒeeŒeU๋ซ 5>4&'462"k8HH8&//&ชeŒeeŒฅ\v\, BTB ฟŒeeŒe@๋ภ !6463253#"!2#!"&5463ซ&k@&4๏€€€ฆ4&„+–%*ึUึ*++ีี%'%#!"&=463!2'!5!5!U€ชVU+ชVซE‹›ซซฤ**€++U๋€ '7%537!5!5ห •a AีชV –` @**ี++U++ ภ(%'7'7'3##5#535372#5!!##5#"&5463` @@ ‹@@*@@*ซ+€@+ชk€` @@ K+@@+@Uชช*++@๋ภ#5!2+#5#"&5463Uช€€kชk+++–+++,,ีิ #'5>4&'57&'&'7#6?'67ีnQ?VV?Q๎%/?U)M+)/%3?+??ARคz+a€a++น/$3?~%/?3")ฅ//1@Uภซ%5!2#!"&5463•ึ*ึ€ีี++Uีซ )-%5#'#35#5#'54&+3532672#!"&54633#ซ56† +K +๋ช ++ภ€KK€KK€€K€+ญ‹@๋ภ%5#2#!"&5463ภภภ€k€€Uึ*@๋ภ%5!2#!"&5463ภ€€€k@@Uึ*@๋ภ5#5#%2#!"&5463ภภภ€€k**V++ซึ*@๋ภ5#%2#!"&5463ภ€€••ภึ*+Uีซ37#"&5463!@u ช‹vซ@€ภ€ %5#5##335?'#!"&=463!2+@+@@+€UU  ๋*@@*@@5U๊UK ึ @๋ภ%5!2#!"&5463ภ€€€ซ๊๊ึ* +@ีภ#'+/39%#57#55#3#3#5#5#5#5#5#5#5#5#3!3€+++Vซ+++++*******++++++++ซีVีภ++U**ชี+*++***V++U++U****V++U++U**ี€@@ภภ7632#"&546;20]/ $( –ี K  ]0/  K ี– ($ ฎk""/&476 "'&'&=&4. 5i"i5 2@B5dd5BUU••3#5'7#ภี*๘๘•ี๘๘wM‰ต 7#'75'7 ``K€t€IIU``ˆ€sw๘HH@ภk '#53#ขภ•+ซbwkภ•bซ+wkkซซ3#537ซ๘ี*๘๘*ี๘UUซซ#5'5;''7ี1q*e1ึ€1>>ซ1qดขe1€€1>>++ีี 5!5#5!572#!463€ซซ+ีUU++€++k++•U€@•ภk !!5!%5!•+ีU+*k+ซ++V**++ีี 5!5!5!%'!"&5463!2€UUีVU++@++@++ึ€U+ี#'%54&"6"26472#!"&54635!!5kIDI(({ชVช•  ((WU++++@@ภภ &26%2#"&546;27675#'3##'#535#53#7#5ซ –ี K  //^/ $(@+@@++@+Vต K ี– ($ /\1/  เ+@+@@@+kk Uซ๋'/7?GO2"&42"&462"&42"&4&2"&46"&4622"&462"&462"&42"&4๏""""š""""f""ผ""ฤ""""""š""๋""f""""f""""ฤ""ฤ""š""š""š""+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ++ีี +463!22'#"&=!5k ีU  U U๋  V+ @ ภU +ภk@•ภ %3'353'##5#U@UU@*•U@*@•UU–•U––@ ภะ'7'5''#"'.7'71Oy&›fLํ:/W$M lDG. EAR DฒgD.W>0ENk+•ี264&"&2.54๊,, |W,+4'  ,,ซW>PF=EAR>++ีี 5!5!5!2#!463€+ีUU++@++@++U€++ีี 2#!463ซีUีU€++ีี%!72#!463ซช++ีUซี+*U€3รภ '+"&='%'732Nu( ึ8b๒2–ญŒ)๏8๙๒2@@ภภ7632#"&546;20]/ $( –ี K  ]0/  K ี– ($ +5ีี"2B'#"&57'.547'.547'"'632'654&'654&#"'632Fe " *#!.'19,,ี+%2=X} e1#2# #5Kห›ก"1%;#&.6.O%c:J9-}X=2')Fe# #2# K5@ภ/%&4737'7'54&""26472#!"&5463}# *,,* uXPXš4&&4&๊Vี,*+!4,4!+*U%%&4&&4fึ*@ภ #)5#54&""26472#!"&5463'57ีชXPXš4&&4&๊V•@@@@€€€%%&4&&4fึ*k**++Cี *&'77#5'7"'&'&=&""/&476 ‰JLn*ุLLU5 .h. 5i"/JLˆjj[KL5BB5d[+ฅ๋#%5#72+"&=463&2&"'62&"@€}  z  #ภE8ž8:,~,ZUซซี ฯ  ฯ ภE88=,,k๋•!!%463!2#!"&5•ึ*€€kึึึk•๋%#2+"&5463kึึึk*ึ€€k๋•!!%463!2#!"&5•ึ*€€kึึึk•๋%#2+"&5463kึึึk*ึ€€+kีซ!#"&=4&"3'3546226=#€U@2F2"@UU@2F2"@ซV•#22#••UU•#33#••++ีี 5##5##5#%2#!463k++*++ีU++++++ภU€•€#$264&"264&"$2#!"&4623&54l>++>,๊>,,>+/bDD1๊1DDbE`ภ,>++>,,>++>”DbEEbDD1+ +1€๋€6264&"73##5##"&4632„""ฃ+U] B*5KK5*Bี""ภ๋T2+"&=33##5463264&"7#'+"&7'&'/4?5'&?636?6;276•ี+ีี+&""|+ +๋€@+V+@๊""%  $%  $@๋ภ #7##!2#!"&5463ี*UU*V๋€€€UUUA,ิVึ*5๋  &%5&#"6322#"'&#"&#""#"&56326ภ!*A44A''L*)=A4+J60+KJ++u๖ ๕  ษ 9 +Uีซ7!5'2#!"&5463ซชVซซVชkีjjี+Uซ%7'633!53"&=463!2VVi+U–UUVหPP.n:z++ีี!%767'''!53"&=47''7'"'!23#•1" nฅ:}U!ฝv"Vp;ภ( "ไ[:+ึ !๊ v O-o+@ภk 77#53#5@ขwbซ+•Mขw+ซb•UUกก2#44#4&#462#"&UX|=Y>‰ร<Ÿq()|X>Yตร‰qŸแ(kk•• %##5#53533•€*€€*€๋€€*€€@@ภภ %5#5##33572#!"&5463kV*VV*€ึ๋*VV*VVีึ*++ีี %5#5##335&2"&4kV*VV*mฐ}}ฐ}๋*VV*VV๊}ฐ}}ฐ++ีี6264&"2"&473##5#535บŒeeŒeSฐ}}ฐ}๊VV*VVUeŒeeŒ}ฐ}}ฐV*VV*V@@ภภ !'!7#5##%#!"&54?63!2m&uJVJ+ ึ   •เu++ ๖  $ @ภ %'7''72#!"/763•LLLMMMML^ภssณMMMMMMMM+ึญญ++ีี%2654''7&#"62"&4Fe$๐-o$๐-2•*ึ*ž  €ึY * UV@@ช€   ชV@@ภภ '7627#บ'P'2€์P์Pj'P'2เ์P์+Uี๋%7'#!"&=4?ฐฐฐ…ชมม๋nggีี qq @€ภ€ 75!%!!53€ภ€€•V๋**•+ี++k@ซซ 3#'##33x–x*ภ€ี*•kUUซซ55#5ซซซUVซซVช=@ภภE%267/67632327676323##"&5467.#"&54>76'&( ภ% #  09*)54>!(<7 ;  #2  t%&&  ๙$ ('$$<7"5E>'!H  I 0$$"@@ภภ %5!32652#!"&5463•ีV&4&Uีภีี&&ึ*+•ีk#2+53264&+553$;#"&46;#"k,>>,VV''Vjช'VV,>>,VVk?X?)'6')€**06')?X?)+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ!ซีk7#7&#"'>32‰LภN0>8Y2uIUMภM(A4DVk๋•%!5!•ึ*๋*++ีี %5#62"&4kึฐ}}ฐ}๋**๊}ฐ}}ฐ++ีี6264&"2"&43#บŒeeŒeSฐ}}ฐ}jึึUeŒeeŒ}ฐ}}ฐC*@Uภ•&#'7ีhqMž••@[mW••U•&#'7'7hqMž••€UU••@[mW••@UU@••@@ภภ %5#264&"#'57* lpp pp๋€€\ p pp p@@ภภ 5#264&"#!"&5463@ี{4&&4&ซUึ@UUี&4&&4/U*@@ภภ  $(,16:>BFK35535353753'5353'5353#553"&532#'#55353'53'463ภ€ซึ++++*++++++ี*U+U++*+€*ซ+*+€++@€€ซึึ++ซ++U++ซ+++ซ**ซ++€++ี++UU+++€++++ซ**ช++@๋ภ75-5+@ภภ@•++•ภ@€ภ€ 75!%!!53@€€€๋**•+ี++kk•ซ 3#73#'!!(P],e e,ส*ึ€k&/๋๋/Z*+ซ฿k2.#"#56 It2Y8>0NภL?UVD4A(MภM7++ีี%3#3732#!"&54637T-m(m-xoช,,u๊@ ชV๕vv@@ภภ '3535!32652#!"&5463UUU*VjีV&4&Uี+VV@@kีี&&ึ*@@ภภ !'!33537#!"&54?63!2m&uJVJA ึ   •vu++ั ๖  # ++ีภ #?'3=232#!"&=46;5463๋UU@@VVชVuVU@@ **+*๋๋*k๋•'2!54&'54632#!"&=462!54€/"€"*•. ,, .jkk@@+Uีซ!537353+"&%3#53#3#+@Uซี€€€••UU•**๋ีี‘+€*€++Uี•646;#";55#"73#53#53#+Q9KK(88( @@ 9ฏชชชชชชาrQ*8P8*@@+++ + *++ีุ6264&"62"&47'5''7'7ย|WW|WE pp pหUeCbbHbUX|WW|qžqqžp2<€cR RR!S ++ีฺ6264&"62"&47'5''7'7ย|WW|WE pp pหUdBcbHcUX|WW|p pp q3>€bQ QQ T ++ีี '5264&"2"&4 `p1ŒeeŒeSฐ}}ฐ}kp9D€๊eŒeeŒ}ฐ}}ฐ++ีุ #3##5#535264&"62"&4%'7'7@@*@@)|WW|WE pp p•bหbb@@+@@+@๋X|WW|qžqqžL!S R R++ภี7''5755?/5462@P{+KJ*ชkซDงฐzO  u5*Pk*งN  ++ภี''575575462ู็ซ+KJ*ชชซ@•5u  u5*ku  uk•+kี%5#5#2+"&546;53***c  œ  #VีkkU+++ น  G **•+kี?#5372+"&546;53๋U+U+c  œ  #VU v เ น  G **•+kี2+"&546;53N  œ  #Vซ น  G **•+kี2+"&546;53N  œ  #Vซ น  G **•+kี,%654&"34623475#2+"&546;531&4&  "(b  œ  #V๑&&  a))* น  G **k+zี%'73#5'7'753=((=\\zbwwbค)QQ)\\yขbwwbข@+ภี ''73#5'7'753'7•++*.((=\\zbwwbk*++++++\)QQ)\\yขbwwbขี+++U+ซี %7/'#5'7''53'7((ข81\bwภ*zA"|()ศ1[ขbw E+kyA#@+ภี!%'73#5'7'753'64'7((<\\zbwwbฬ!V1 ค)QQ)\\yขbwwbขd2z4*X*V1๑๑%3'#3737#'#5'7537371)E*E)D‰FFdGGdFFdGGdฤซภภ*rGGdFFdGGdFFนNN๑๑2"&4264&"%#'#5'75373F22F2 jKKjK+FFdGGdFFdGGdU2F22FฃKjKKj|GGdFFdGGdFF๑๑6264&"#'#5'75373หjKKjK+dGGdFFdGGdF€KjKKjdFFdGGdFFdG๑๑%264&##'#5'753735KK5ซdGGdFFdGGdF€KjKวdFFdGGdFFdG++ีิ%27#"&5467'654&'H,8@lX}oQ6JWSQo8 J6k8!W}XSy@T8>WiyS0'!8TUซ๋ '%53+"&=37'7'7'7%#54632#5k*ึ*@bbCทCCb๊*ึ*k*UU*QbbDbDDb•*UU*Uซ %5#72+"&=463%3!535463!ีUk € ๊ึี+€•––ภ ี ี +๋@@๋+@๋ภ '#57#5#57#5!2+#5#"&5463•****๊๊๊€€kชk++U**U++U**ภ+++๋๋'6264&"%3##5.'#53>7532"&4ย|WW|WT,,`C*C`,,`C*C`F22F2kW|WW|S*C`,,`C*C`,,`2F22F๋๋6264&"%3##5.'#53>753ย|WW|WT,,`C*C`,,`C*C`kW|WW|S*C`,,`C*C`,,`๋๋/%'327'#5.'#53673#'654&#"'6753[ัW>2๒e,-7*C`,,%S,, W> %*C`Šั)2>W:›,%,,`C*7-d*' >W ,,`๋๋/%'327'#5.'#53673#'654&#"'6753[ัW>2๒e,-7*C`,,%S,, W> %*C`Šั)2>W:›,%,,`C*7-d*' >W ,,`๋๋6264&"%3##5.'#53>753ย|WW|WT,,`C*C`,,`C*C`kW|WW|S*C`,,`C*C`,,`@+ภี 3#3%533'3•++U+ี+€*€++VUUVVชชVU++ีี%!ีVช+ช6๘ภ '07'62ดตตDyyเเTUU++ีี,!3#35#"&5475463!2#!"&5463€U*ช@"–ชVช€+ชช0  0ีVช€ชV++ีี"'073#"&=53+52#5#5"&4627!#546;U––€*––*–Vƒ@+@+*–๋–*––––*€––*}€O9U+––*@@ 3#53'#5353cyy]ซNxซซซซyxซซxNซ]ซซk๋•!1354&""&=46354622#5!2#!"&=463ๆ4 $ jึU€+ Š @  @ ึึึึk•๋1%#2+"&5463354&""&=46354622#kึึึQ4 $ k*ึ€€€ภ Š @  @ ๚'?354&""&=46354622#7"&'3%"'&4?62'7'7fI , ๕Qd“ Ep ˆ  ‡  4-y๒x/ห š V  V ๕Qˆc;aŒ  ‡   ˆ 4,x๒y/ '?"&'377"'&4?62'72#. Qd“ Dัˆˆž ˆ  ˆ Qd“ D6Qˆc<`ˆˆŸ  ˆ   ˆ Qˆc<`U+ซี 5##5##5#72#!"&57€+++ซUVVVVVV€ช€@๋ภ&%!2#!"&5463"&5467363232#ภ€€€€&!.*j,ิVึ*๋&$+%,++ีี7+ช+ชV++ีี 7#353'53+ชU+***+ช€ึ**Uซซ3รภ '+"&='%'732Nu( ึ8b๒2–ญŒ)๏8๙๒2++ีี! !ีV€็ีVC็ี๋ '!7'%'fo*…ฝ‡uท +ฝˆf‘ท6๘ภ%62๘yy65UU +ภ#9%7>2&#"54&"32+"&=4635462KK๗  $'-.-'$  ,-=ŠV k .ห8^6      8=M  U U 6๘แ '67''632F HGS๘!-+อt-,แ ๆJGg5,[‘ +Uีซ 355!%5#'!!355!U+Uชซ+*ชV*+Uช**@VV–**@Vภ**@VVf&•๋,3#+"&5475#"&=&546235#7#35#@U@(@&@+@@+@kV*AA, ,ชVVช*+ี)%54&"32+"&=4635462'"6 &ีV k , 6,>Kp p9ซ  U U j>,=cUUUL+2ีภ-52'>54&".54'654&".54622"&4จฐ}91'/dŽd.'19U#*2F2*#KjK‘""ภ|Y:c%O.FddF/N%c:YY#;%1#22#1%;#5KK ""•€๋'3"&5462"&=326=4&"265` DbE3F2,  , 3F2€๕1EE1 #33#เหห เ ๖#33#‡@`ภ(#5.'332654'&546753#&#" )"@!+/;@d* @!#/4  '..*!-$A)./,-@@ภภ 5#5#'5#5#!!•€€€*€€€+€€€€ช€€ช€€ช€€U€@@ภภ #'+/37;?C7#55!%#5%53'3##553'53'#5##5#5'#5'#5#5'#5#5'#5k+€ซ+U++++ึ+U+++U++*€++*€+ี*++€*++ภ++€++ี**+++€+*++ึ++V**ี++++ซ**V++U++ซ**ซ++++U**@@ภภ #'+/37;?CGKOS5353535373#5335353'5353'53'5353535353353'5353'53'53@+++++€*€++ช*€+++++++ี*ี+++++++++€***€+++++•++ช**ซ++U++++U++++++ซ**V++V**ช++U++U**V++U++++U++U++ซ**ช++ 5!!'762#57บ*P*2EึPึUUช*P*2EึPึ@@ภภ #'+/37;?C%53#53'53753'3#5!53753#5#57#5##5##553'535#553•+€+€*€++++ซ€ี*€+ซ***€+€+*+U+€+++@++++U++ซ++€+ช**ซ++U+++++U++U++++++€++U++ึ++ี++ @@ภภ#'+/37;%53533##5#535533#53'#5##53#5535#553#53•+++ซซซ*ซซU+*+++U+ี+€+U++U+€+•++U++€ซ*ซซ*ซ€++€+U++€++++++ี++ึ++ี++++@@ภภ #'+/37;?C535353'53'3#5353533753'5353753'53'535353@+++*+++++++€+*+€+*++++++*********•++ช**ซ++ซ**ี+++U++++€€ซ**ช++ซ++ซ**U++U++++U++@@ภภ #5#5!!!%#5##57#5ภ+€*ชึ+€€+++*****U++U*ึU€ี****V++@@ภภ #'+/37;?C53'5353753533#'5353'5353'5353#53753'53#5353๋*****++++*++U+€*ี+++++€*ี+*+++€+*+@++U++ช**ช++ซ++€€ซ**V++ซ++ซ++V**ซ++++ซ**ช++++ซ++@@ภภ !53%!!#%535353#53353#53•+€€ซ+U+++ี*€+ี+€+@++€+ซซ**V++U++++++++@@ภภ #'+/37;?C%53535353753!!53'5353'535353'5353'53#5353@+*+ี*++*+€€€U+++ี*ี+++++++€***€+++๋**ซ++++++U++++ช**U++ซ++ซ++U**ซ++U++U++ซ****ซ++@@ภภ #'+/37;?C%53535353'3#5353#3753535353'53353535353@+++++*+++++++ี*€+ี+€+++++*+++€+++๋**ซ++U++U++€+ช**ซ++€€U++++++U++ซ****ซ++U++U++@@ภภ !!3#5!5!%3#@€€UึึU€€€ีึึภ+*+U**ซ++€+@@ภภ !!5!5!5!5!@€€€€€€€€€ภ+U++U**V++U++@@ภภ !!5!%5!'!5!5@€€€€€€ภ+ซ++ซ**€++ซ++@@ภภ !!5!5!5!5!@€€€€€€€ภ+U++U**V++U++•€{ซ%264&+53264&#+32 K@  8.-"—†$1ต@ภ@[4"/+2H+@ซ•!#'7#/'#7'€+|"-3<:4y!@4”•@P,$<หyN{”!5!!"&5467%3'#"/&4?'7•+" ๅอgŒ u v n3UU / & -fP  u u  n3U=จผ '#"&547''7p89%/5KG+ท:+Gศ8 K5"6GŸ ธK6{ภ 3/3#'##!!อf3*u0†0u‡9ี@@@U@@ภภ 753'53%!!5!%753๋ีีี€€€€€UVี๋**U++€+ซ++ภUช++@@ภภ 753'53%!!53%5!๋ีีี€€€ซี€UU€๋**U++€+++ภUUk++€€€ซ 3#3#537#ีซ,5k+UK5ซซ++ซซ5€U€ซ #3!57'5€•jj•‹‹ซ@kk@+€€+@@ภภ %5##5##5#2#!"&5463k++*++ึ•VVึึ––+ึ*++ีี 5!5!5!2'!"&5463€+UีU++@++@++€UU+ซี3/3#!"&546vv•ซ€@u €V++ีี %$"&'3&"&462"&462264&"2"&4%J; ฺ žƒฑŒeeŒeSฐ}}ฐ}‹)!!aำeŒeeŒ}ฐ}}ฐ@@ภ๋%5!332#!"&546;533#5•ึ๊+ึ+ชkk๊๊€+ึ*++ภkk+•ีk#2+53264&+553$;#"&46;#"k,>>,VV''Vjช'VV,>>,VVk?X?)'6')€**06')?X?)@@ภภ7!'#!"&5463!2ตJ*`Jีึ*เ`€`5*wM‰ต 7#'75'7 ``K€t€IIU``ˆ€sw๘HH++ีี '!"&5463!2ีUีVซ€U@@ภภ '7627#บ'P'2€์P์Pj'P'2เ์P์kU•ซ ?##5'!!k••U€U*ึี––€€ึ+Uภซ@3!53!€+ช+@€€U@€ภภ873254&'".+5!##"'&'&574#"#4&5&547632ศ>1 อ€SC$'ฎ3$ b "15่6$++=2x-  %)U@ซภ 7!!%'353UVชUU@*k+ซVVีีUซ๋ !!%'3537##5UVชUU@*jUU@**ชUUVVึUUVVU@ซภ !!7##5UVชVUU@*ภ+€VVีีU@ภ•2+'73264&#!5%!553k#22#+@@0ๅVช€2F2+@@+"*€**ึ**W@ฉภ"'#5.'3327'&5'"'6753#&r7/!@!,/<%KSIด@ #/ฉศ0..*!-J;I!./,-Uภซ@75!5!5UVชภ++€++๋๋ "&:73'##73#735#5#'535#5##35#35#3#5##535#53353ไ8%K#IH"`++++***ึ***++€++€ึ€++€ึ€๐Rm*ภภ๊+€+++*ึ**ึ*+++€++Uึ€++€ึ€++K+ตี 7%773#5!#5j--ร-s**k@€t-..-€@ภkk@jj+หี52#"'#"'##"&463236236 $ > H > $$ > H > 5, , Uภซ@73#5!!UึึVช๋+€+5kหซ##5#5'!###ห@@@ึk@k@@••@k@++ีี"*%654'&546323&'5##"'#32"&4CZ8-*;9%Y95*C9vฐ}}ฐ}~ 6=(9**#8 ! (9*€}ฐ}}ฐkk•ซ!###k*u@uซ@+€ีk%646;2+"&46;#";264&+";#"+D1เ#22#ต  ข ทเ,,หห1ฤbE3F2, +",>++Uซ#!"&5467>32):?,๋5KB0L-:X*=*,?K51I'0I++ีี%264&+4&#"'"32"&4` 2#-&&ฐ}}ฐ}ซ,#3%&4&*}ฐ}}ฐUซ?''%#!"&5467>32ีo,):?,๋5KB0L-:X•n,J=*,?K51I'0IUซ%#5##7#!"&5467>32k@V@k):?,๋5KB0L-:X๋UUkช=*,?K51I'0I+ซ+#";7'#"&5467'654&+54&#"'632ฅ%#22#ะ๐e+๚5KH3"):-!& D1 '/:X+3F2›*K54J+=*7 && 1D IUซ&%264&+54&#"#"3%#!"&5467>32•&& D1(> #22#):?,๋5KB0L-:X€&4& 1D0%3F2ช=*,?K51I'0IUซ%3'337#!"&5467>32+@kk@Vr):?,๋5KB0L-:X๋jjV•=*,?K51I'0IkU•ภ 7!!%'353k*ึ*••U€€+๋••€€kU•ภ 7!!75#7#k*ึUU••U€+V€••€+Uีซ32#!"&5463ี+ซชซ+ี+Uีซ%5!2#!"&546;ซชVช€+€ีีี++Uีซ!%54&"6"26472#!"&546;•;4;f""@ช€+•ซ""Zี++Uีซ %5#5##33572#!"&546;•@*@@*Vช€+ี+@@+@@ซี+@๋ภ'2#4&#2#4&#2#2+53!#5463aŠ+qO>X+?,&@ซ••€++ŠaPp+W>,?+&€ึ+*@@@๋ภ!).2+53!#54632#4&#%#.'52#4&#2#ภ••€++aŠ+qO€x_?V>X+?,&@ภึ+*@@•ŠaPpkึ?`#–W>,?+&Uซ!53!53"&=463!2UVUUV€ีี++ีี+๋ี%5!2+#57#"&5463ภ€€•*ช*•ีึึ@@+๋ี%!2+3#535#"&5463ภ€€•*ช*•ซ*+**++@ีภ /3#'3#73#3#!%#3#3##!"&5463!23UU€kk€UU€kkี€*****ี+*€ึkk@@V**ึี+*++***€€๋%5#2+"&546353Uชชชชภีี*ี+*++++ีี 3#'7##5%'53`uu@`@@€ @@u@@€@€@`@@u@@€ @@u@Uภ๋2+5354&"3#"&=4ฐ p&@UW|WU@&๋qO–&ซ+>WW>+ซ&–O@ภ๋2+535#5354&"3#"&=4ฐ p&€•UUW|WU@&๋qOึ&+ซ+>WW>+ซ&–O +kี• #'+;5#5#'5#5#5#'5#5#735'3535'3572#!"&=463•*******ช***@******–ช+**@**@**@**V++–**@*****@**@**@**@ึึ€ป€Y7'žbb€€Ybb€€ซ‹I‹%'7I€€bฉ€€bท…U…?'7ทbb€€ฃbb€€€ท€U7'7'ž€€bท€€b@€ภ€!'7ภาL€€L*M€€M€€€‰ 75!''7€€b€€€++อb€€ +ีภ "&*.>%'375#5#'5#5#5#'5#5#735'3535'3572#!"&=463Uช@*******ช***@******–ชV๊++@++@++@++U++•++@+++++@++@++@++@ีี+€ภ€ 3!'7!•+ผM€€Mk€M€€M€ี€ 3#'7'7!5!ซ**ด€€Mั/€โ€€M*k+•ภ3#5.5326"&=462q$K5*5K$C\CW4&&4&6QFFQ6/>>&€&&€Uซ!53!53"&=463!2UVUUV€ีี++ีีUภ%5!5#!3!53!ซชึV++ชภีี@++@Uภ #6264&"!53#!"&53"&=463!2๗  –VUVUVk  ๊๊๋๊๊Uภ!53!535"&=463!2#UVUUV•ีี๋++ีี@@ภภ37;%5#%#3#+#5##5#"&=#535#53546;533533235#7#5kึ++++++*++++++++*+++ซ*U€•ึึ€*++++++++*+++++++U**U€€Uซ้ #465!"&#๋–WWVeŒeภ?W–้ฉA`฿UUFeeo`Ak•๋%#5#2+"&5463pเ›V€&&ช&&€+ี@ซ&ช&&V&k€๋ %#264&"2+"&5463UภSvซ€+ีUญ – j Uซ %5#72+"&=463%3!535463!ีUk € ๊ึี+€•––ภ ี ี +๋@@๋+#"&2+'35#'5463%3'!53547'!'!๋ @.U+ ๊ๅ๏Q+"2†+ 'ว็+DU ี @–o+Y %ๅHQิ"2@๋ 'B+@@ภภ )3%5##5##5#%2#!"&=46;537&#"'632&#"'632@+ * +ึี+1! ()1)10)-=>-€++++++kUUUU† $$--@Uภ•%5##5#%#!"&=463!%7•ี+*; ึ ิ•++++‡ uUm(@ภ๋ .=>7#5ภnRRnภ;R••๋V€Y““Y€•ฟiCผBzU+ซี %%5#'5#5#'5#5##5##!"&5732k++***++ึ+€+€ซภUU+**€UUUUUU****@ช€k•๋%#2+"&5463kึึึk*ึ€€k+•ี)2"&4264&""3264&72+"&5463ๆ4&&4&X??X?kZึ&4&&4…?X??X""*ชVUซ๋!13#"&5462"264&"6"326472+"&5463€ีี  ,, F22F3g"/ณ•ซ+Uถ, , 2F33F๎$$Dอ3U๋ซ%!2#!"&5463•ึU€€+@ภ%!5#2#!"&5463›สฦVซ&&&&kUซ@ี&€&&€&+ภ %!264&"2#!"&5463•ภ“ถีkUซVุj–๋๋ "&53"&4632#5462E`F๋0EE00EE0E`F0EE0E`F๋E`F๋0EE0@๋ภ%!2+#5#"&5463ภ€€kชk•+++Uซ264&"#'&4?3€KjKKjเAชAAช5jKKjK.ค4zz2จ2zz@@ภภ%3#5'#5375.5462kUkUUkUV&4&ซkAZZAkUD && D+ภภ@ %53!53!53'!!Ukkjj•kภ++++++€+U๋ซ)7%5#72+"&=463264&"7#5&5475'3#"&5463!ภUj€ wJU€UU€€ซซี ีี สb&&&&€++€๋€+264&"264&"'5#5##335%2#!"&=463’B@@+@@+€R*@@*@@•ชช++ีี $5#5##33572#!"&5463!!"&5•U+UU+kV+ี+UU+UUภUี*+++ีี"&462264&"2"&4@&4&&4`ŒeeŒeSฐ}}ฐ}4&&4&๋eŒeeŒ}ฐ}}ฐ@ภี%7/2+'#"&5463(XX((XX(•U@@Uํ((XX((X@ี@@+k@ซซ 3#'##33x–x*ภ€ี*•k€@•ภ3###"&4632•U6%(88(ภ@๋$18P8++ีี'/7?GOW_go$2"&42#"54264&"2"&42#"542#"54'"54322"&4'"54322#"542#"54'2#"542"&462"&4"   fŒeeŒeSฐ}}ฐ}@ 5   @ K J 6 A    ๋  > KeŒeeŒ}ฐ}}ฐ# U €  , € @ • J  b  @@ภภ%)19AIMU]e$"&462&"&462&"&462"432'"5432%!!"5432"&462&"&462&"&4625!"&462"&462"&462      @ ห€€+ ฌ  ]K€๗    ]•  I  I  WV u+๕  >C๘++  a  l55หห !'/TZbjrz€†Œ˜ 62"52"&42#"4&2"52"&4$2"52"&47'"&4632'#"&5467'"&4632#"4"&462"&462"&462&"&462"542'"4323"432.'5462#6"&4625B  j –B  J้  ‹[P  <  <  ฅ i      ท  เ๖ K     เ @  >๊ @   @  ฅQ  <  <  ๔  b  b  Ÿ  W Šช  `  55หห%-3;CKS[ciou{ƒ‰‘—Ÿงฏ2"&42"&42"&462"&42#"462"&462"5&2"&42"&42"&462"&4&2"&46"&462'"4322#"4&2"57"432"&462"542$2"&42"562"&42"&462"&4?  v    >        ฃ)   –๖   ‹ฌ  6B      KCM  ยํJ  W ภ  I  Ÿ  b  ‰  >–• เJ  W j  T   Ÿ  b  ++ีี6462"+}ฐ}}ฐจฐ}}ฐ}k+ซี 2#"'>4&'6ีY}}Y:019910ี}ฐ}ctc€+•ี 2#"'>4&'6ภX}}X"BSSBี}ฐ} qŽq ๑๑ %264&#"7#'#5'753735KK5!))!ลFFdGGdFFdGGd€KjK ?L? วGGdFFdGGdFF๑๑6264&"#'#5'75373หjKKjK+dGGdFFdGGdF€KjKKjdFFdGGdFFdG๑๑%264&##'#5'753735KK5ซdGGdFFdGGdF€KjKวdFFdGGdFFdG๑๑2"&4264&"%#'#5'75373F22F2 jKKjK+FFdGGdFFdGGdU2F22FฃKjKKj|GGdFFdGGdFF@@ภภ #!"&=77'''5463!2€@ึ@UV•@UVU@* @aŒ@VVŒ@VVVAa+@ภภ '7622#"'2654บฟ;ฟ4&2#4!ฟ;ฟๅ&#2+++ีี %7>7#"'3.#&54/7#7'632าNN;LทฯO.C l ฆ8f›ฯO.C๔N;L0ˆ#‡0•‡GปS(S=ฐ ‡GO‡0+Uีี#6264&"332#!"&546;462"ิX??X?+€'DชD#(8((8•?X??X*ว8((8(k•'+462=2+5#535#"&5463"&4623#•IDI–@@jj@|""jjีเเ+ึ@@+*+*ซ""ๆ*k• $264&"72+5#535#"&54633#"}–@@jj@–jj€""€ึ@@+*+*U*++ี๋ 35#5#'5#5#'5#5#3#+"&546;546;232ซ++++***++++Vชชซ U @++ภ++ภ++ภ++ภ++ภ++ภ@ @@ภภ#+%53+52#5#5#546;3#"&=62"&4•+UU+Uี+UUUUF22F2kUU+UUU++UU+ีU+U•2F22F@@ภภ!*36264&"62"&453+52#5#5#546;3#"&=๏""F22F2๊+UU+Uี+UUUUี""f2F22FธUU+UUU++UU+ีU+U++ีี 3!!"&57!'#!"&5463!2+*+ีภ@V?ฟ€ี*ซUjO@@ภภ4264&"&264&"264&"264&"72+"#"&46h-W-•Oq?,%PppChฎdG,>  p p@@ษภ?'%'#57'7762”ฌ)ฌOC)ฟeพ)Ckฌ)ฌ๔C)พeฟ)C@ภ๋ 2+5#5553#5#"&5463•jjjVjj++jภึภ€+ภ€€@+*+*++ีี6264&"2"&473##5#535บŒeeŒeSฐ}}ฐ}๊VV*VVUeŒeeŒ}ฐ}}ฐV*VV*V@ภ'$264&"62"&4.4673##5#535|WW|WE pp pU.'8HH8'@@*@@kW|WW|p pp $XI.dzd. @*@@*@@€ภ€%5!%2#!"&=463•ึ*ึซชชีชช@Uภซ%!2#!"&5463•ึ*ึ€+๋๋7!##5#"&=#53535#532•VV*ึVV*ึซซ•*VVึ*Vีซ*ซ@kภ•%5!2#!"&=463•ึ*ึ•ึึึึ@•ภk%5!%2#!"&=463•ึ*ึภ€€ซ€€@@ภภ%!2#!"&5463•ึ*ึk*ึUึ*@@ภภ#2#5#553+5'3#"&=46;##•+UU+UีUUUU+ภUU+ซUU+UU+Uี+U@kภ•%5!2#!"&=463•ึ*ึ•ึึึึ@@ภภ%#7!2#!"&5463*K๊:*ฆึ*ึ๚eL3C*ึUึ*k@•ภ%#2+"&5463kึึึk*ึUึ*UUซซ%!2#!"&5463€€++‹ี‹ !!!!!!+ชVชVชV‹+@+@*@Uภซ7%!ˆxxศ€ภ€ีี+ช@@ภภ '7627#บ'P'2€์P์Pj'P'2เ์P์++ีี"%3572#!"&5463#53533##ซช€ภช๋+++**+UVช@**@ชVภ++++*U€•• %#55733#•*@dภซซ€ใ$$€*+€ภ• 3#%23#5767654'&#"#476+ชช4V=ธY  ..*ชK B$ a  1$U€ซ•%#55733##5#535ซ+@dึVV*VV€ใ$$*V*VV*V+€ี• 03##5#5353#5767654'&#"#4767632ซUU+UUึธY   .  kV*VV*Vว$ a   $# จ€Y•)4'&'&"3276=432#"'&'&5+      „XA((((98(9t>,;?๋๋"%!2#!"&54635#53%!!"&5ภี+ี–+U๋Uซ•+ีVี+ีซ*ีีซ+U๋๋(1%#546;5#532+!2#!"&5463!!"&5k€+UU+ซี+ีUUซ๋+U+*+*V+ีVี+Vซ+U๋๋!%5%+535#535#532#2%!!"&5!2#!"&5463kUU++UU ีUซซี+ี๋+*++*  Šซ+U+ีVี+๋๋!%!2#!"&5463!!"&5#7ภี+ีUUซ?L๋;*•+ีVี+Vซ+UqdK2๋๋&%!2#!"&54635#53353%!!"&5ภี+ีซU*++ีUซ•+ีVี+ีU€UUีีซ+U๋๋.%+535#53#32%!!"&5!2#!"&5463kUUU€V+ีUซซี+ี๋+*€*+€ซ+U+ีVี+๋๋+435"&=46;#32#!2#!"&5463!!"&5++VV+€ี+ีUUซ**U€*+*++ีVี+Vซ+U๋๋ #%#7#53!2#!"&5463!!"&5*UU€Uี+ีUUซภซ**ึ+ีVี+Vซ+U๋๋%)9B35'35"&=463"&=46;2#2#!2#!"&5463!!"&5++++ + €ี+ีUUซ**V++ซ    ++ีVี+Vซ+U๋๋+45#72+535#"&=463!2#!"&5463!!"&5@++UU+ซี+ีUUซ@++U€+*++ีVี+Vซ+U๋๋#7@5!!5##5#535372#!"&546335#+535#"&=46;2'!!"&5ภี++*++*+ีV@@@๋Uซ@€ี€**+++ซี+ซU+@ซ+U@@ภภ%#72#!"&54633•••ึ•••k*€ซึ*ซช@@ภภ"+2"&453+52#5#5#546;3#"&=ๆ4&&4&ี+UU+Uี+UUUU@&4&&4ฏUU+UUU++UU+ีU+UUซ*%264&+54&#"#4&"3%#!"&5467>32•&& D1:$)5+2F22#):?,๋5KB0L-:X€&4& 1D/ E,#33F2ช=*,?K51I'0I++ี !!#'#2#!"&546;7€+`JKaVชVUUUี++KKีVี+UU€๋€!77'+ภ*€`"<€ซ€Q๋๋%!2#!"&5463!!"&5ภี+ีUUซ•+ีVี+Vซ+U ,,ิิ %+17?&7677673&"&462&'7#67&'7'&'5'67y%/?i/$3?r+)e&4&&4ร)M+)N)M%/?i/%3?[+%)k&->3ก4&&4&U/$3?~%/?3r/%3?+%)1ฯ๏@6264&"467'.56&5467676'&'.547F22F2z-%"@@ ## @@""@@ ## @ซ2F22FT&E ;!%%#;;#%%!;;!%%#;;#% ๋๋ #'753'7?&2"&43#7'7'#5'7#5๋*..ฆ.„4&&4&ซ€€..U*'.€€€....ศ&4&&4*..c€€‘.s**@ฺี 3'7#'##7!335#g2E)D)EีีUU•@]N*ภ++ภภภ++•ี'53''5#5'k!ตึVฯOXM@j+9ต.ช•ฐYƒภOk•+kี335#•ึVV–@ีชภ @ภ๋ '+/%53'53'535332#!46;#3#"&5%5353•++€+*+ี*€+ซUUUUU+€+@+ซ**ช++++€ึ*ซ+ึ+ี++++ @@ภภ+/3?CG5!3335335355##5##5#2#!"&54633#73#'33#5##5335!#3•ึ*++*++++*++ึ*++ซ++U*++*++€***€€*++++*•++++++@ึ*€+++++*****+++UUซซ'/7?2"&42"&42"&42"&46"&4622"&462"&42"&4ฤ""o""o"";""‘""ฤ""""o""ซ""<""<"";""ฤ""ฤ""ฤ""<"" ๅๅ  $=%3'5'#'5'#5#33'!"&5'35!#'!2'5#'35#'5#'35#'U* J* JVVaูส+ถ+UV+J*+JV*+JV*UJ V€J V€VVa6+J+VV*ถ+*VJ+*VJ+ ++ีี #35#5#5#5#5#5#5#5#5#2#!"&5463ซVVVVV*VVVVV*VVVVVVชUVV€VV€VVVV€VV€VVVV€VV€VV€ชVอห!0'#5'#5##53353'#'32'735#'532#'#5b6ขH + +u๗ (  `++K  หcอขI i55€++u•  ) `i -+@ภภ@ +%5#72+553#5##535#3#'##532**  J@ + @+K  K เ@@` @ €++€55€+  -++€ €๋€6264&"62"&4$2"&4Z""F22F3!jKKjKี""f2F22F]KjKKj€๋€$264&"62"&4&2"&4HF22F3!jKKjKฃF22F3ซ2F22FฃKjKKj 2F22F**ีี 'L%7'6"264264&"&264&"'7'"264#"/"/&4?'&4?62762cNNM  I    $MMNป  eU\ TU]UU]UT\NNMNz  I    NMN#  4U]UU]UT\UU\@@ภภ7!'#!"&5463!2ตJ*`Jีึ*เ`€`5*+Uีซ#%!2#!"&5463#5##5#57#5ซชVชซ+*+ี***€+€++++V**V++@@ภภ"%#53733535#5#72#!"&5463kkk*ึ + ++ ๕ึ• J*ึ๕ ++ ++`ึ*€๋€!77'+ภ*€`"<€ซ€Q@@ภภ $)%463"3463#463"#52653#5265##5+W>,?+&๋ŠaPp+W>,?€ŠaPp€&@>W*?,&@aŠ+pP€>W*?,aŠ+pP&@@@ภภ -363"'63"'657'#47'#47'#527'#52?'65H9?/*B" ‰"ภe=*++85BVE5)2"X " "+B" *?9*/›="2)5DVB58++*X" ++ีี2"&4จฐ}}ฐ}ี}ฐ}}ฐ@@ภภ(54&+3#3#326=4ੰ#!"&5463@VV++VVVี ++*++ ฎึ*•๋€ 2#4&"#462#4&"#4ŸยŠ+p p+ฎzX*?X?*€ŠaOqqOa5X>,??,>@@ภภ %5##5#32#!"&5463@+*+U€ึ•ึVV€V+ึ*@@ภภ!5#3#326=4&+572#!"&5463@€UUU*ชึ@+€++++€ึ*@@ภภ#'5#";26=4&+572#!"&546353@U**ชึ€*@+€++€ึ*++@@ภภ%5#32#!"&5463+V+•ึ•ึ+ซ+ึ*@@ภภ$54&+3#"35#532672#!"&5463@UU*€U*Uึ+++V++ฝึ*++ีี6264&"2+"&473##5#535บŒeeŒeSฐ}ซX}๊VV*VVUeŒeeŒ}Xซ}ฐV*VV*V+@ีภ/%#2#5"&463264"32#!"&546;73ซซ->>-((((->>-ซชE&€&k=Z>&(:'‰(:'&=Z>*+++Uีซ3#!"&546;3'33'33€Uช*@**+@+++@ซีVVVVV€@€ภ 3#"&4632€U3F22#ภUึ#22F3 k+™า%3!535.5462€ึ€6GX|WLจS**T S7>XX>9T++ูา%"&462%3!5#546;2#35.5462nYL8@ซ @ ซ6GX|W9TS*jV V@T S7>XXซ€I€'7Ibb€€bbb€€ท€U€'7'ี€€bb€€€bb@@ภภ4264&"&264&"264&"264&"72+"#"&46h-W-•Oq?,%PppChฎdG,>  p pU๋ซ7!'#!"&5463!2ตJ*`J€€๕`€`5++ีี6264&"2"&4บŒeeŒeSฐ}}ฐ}UeŒeeŒ}ฐ}}ฐ+Uีซ!2#"'&"#"5432276#"'632ษ `ฦ` `ฦ`N]XSRYXSซฦ##:##7่U+ซี 73&47##!"54764'&543!2Œ่่ฦ##:#USฐSRYXk `ฦ` `ฦ+Uีซ2"/&4?"2764'ณš]]š]๐ŒVVŒVซB†BB†B9r99r9@@ภภ7!'#!"&5463!2ตJ*`Jีึ*เ`€`5*U+ซี 7!'/7572#!"&5463€R@.@56•kmR7๎ซ ซ*ชV+Uีี#6264&"332#!"&546;462"ิX??X?+€'DชD#(8((8•?X??X*ว8((8(++ีี 3!!"&57!'#!"&5463!2+*+ีภ@V?ฟ€ี*ซUjO++ีี $0@53'!!"&55375#3535#554&+326'54&+3532672#!"&5463+๋+ี•เ@ 555k 5  ถ @@uี*+K €* @@ €7 €*ญ@@ภภ#%!2#!"&5463#5462&"&462•ึ*ึ๕ภB,++,>ZZQ้NNNŠŠ‹r8Ÿ88 ,+|,,EZ[EพNNN<ŠŠŠWWซ๋ 5>4&''77&'&'7#67?WW?.==.aa~09A+ฉ`‚`+G^GS_aŽ+Ž%._-&UWฉ๋  %673677#&'7'5.4675h+r$0”+Wa.==.?WW?ถ.%พ%G_SG^G+`‚`B@@ภภ%!2#!"&5463•ึ*ึjkkk*ึUึ*kUU€๋€#%5##5##5##5##5#%2#!"&=463ภ+*++*++*+€€ซชUUUUUUUUชีชช0ล &75#"&6264&"#"'&54?632.?}J"  C  jž  ส4[‡ฒ๛  ฆ!A  Aฒ }ภ+Uีี %7'#55372#!"&546;73@KK€KK€kชD'€'ตKK66KK6ภ**+kี• %7'#553?'#!"&5463!2KK€JJ€kUU ี + ตKK66KK6JV๊VK  ++ีี %$"&'3&"&462"&462264&"2"&4%J; ฺ žƒฑŒeeŒeSฐ}}ฐ}‹)!!aำeŒeeŒ}ฐ}}ฐ@@ภภ ?7+73546;5%&'ฦ๚ฝฝ*Uซ*Uฃ=๚`ก@๚=ฝ+U+U*๚=ปข++ีี6264&"2"&4$"'75บŒeeŒeSฐ}}ฐ} LJj&ZUeŒeeŒ}ฐ}}ฐ(LhL&Z€~‚U\%54'&#"27>'432#"5%"3#"'&533254.'&'&547632#4'&%73#5  ,{S)RS , 9)&!# * !** Ie+@ๆ6$45   Gm9)nn5  %'    &:%อY~ย‚8^"23#"'&533254.'&'&'&547632#4'&"&53324+5327654#"#47632v , 8(!* !** ก*J/+ *. & *0&*!  %'   &*%&" R! & -$H@+ภ๋6264&"%"&4632753#5ย|WW|W+*p ppPA7ษ*+€UX|WW|ต5BOqqžq+ ฌ€€++%ภ๋/%27''#"&547'537#5'654&#"'632'$ฬW‚{628Pp ;ฦ*+€ึ* W>(" 0:C5Uฬ")>XV…6 qO:0;YI฿++L5B:0")>W *++ีี !)%67#67#67#53&/3&/3&2"&4ฅ”p  >” p>J?WWฐ}}ฐ}ี@>ภ@>ฒR`‚`v}ฐ}}ฐ+ี๋532#5#3'35#"&=#535#7#!ี€+€U+@@*ชUU+@@**U+€€ี+@@+ช++@@@@ภภ #533##53%3#5#53#533#3#3#@+UUUีีี++UU€**ซซีีี€€@€+*+U*+€+*ี€+***ึ* @kี• #'+/3#53#53#53#53753'3+5353#53#53'53€UUUภVภUภU๋UภVVjUVภUภUUU•UีUUUUUUUUjVVภUUUkVVVVVVkUU@kี• !!5!!53@•k•k€•€ช••••Uซ!73'#373%3#'#'#"&4632373%7(D+D)D',% &R1GddGR3 " ึซภภ*–ภ‚‚ +4eŒe@‡‡‡NNUซ#!"&5467>32):?,๋5KB0L-:X*=*,?K51I'0I!๋เ!%773#'"&546753#553'7p&@@k#KjK#€๋@ึ*ษ&&}&ส+„:#5KK5#:gภ++??S'' !๋๔#'?532"&43#7'7'#5#57'7L&&*JjKKjK+@@;&&&*–@{&t''5??jKjKKj +x&K&&5??ิ++y&++ีี5#?2#!"&5463!!"&5ซk56V+ีซซ ตUี*+@๋ภ7!''%2#!"&5463k*`J6 €•€`@หึ*@๋ภ  $)-16:>73'/!!"&53#73#'#463#3#3#%2#3#'3##=3#3#@ีD5'`+V**U++€+;****ช++ซ+++ซ++ึ++++k[E.fีU+++++ซ+€+*+€*+€+ึ+ช*++@๋ภ  ',049=A#53#5#57#46#5#5#5#5"&=32##5'#5#57#5#5•*€+€+++e***++ี++ึี+++€+ึ++++ภ++++ซ**ซ+ซ++U++ซ++++ีU€€*++U++ซ+ช**U++@๋ภ6264&"%2#!"&5463นŽddŽdk€€KjKKj๕ึ*L!ด๔ 7'77'7#53/3#5!j&D&B&y**&…**€*V&$&๙'z?_&[?๊€€%17!##5#"&=#53535#5322#.''267"#"&'3ซ++ช+++ช€€d“ D5Q_Qd“ Dซ+++ช++ึ€+€+ˆc<`Q6Qˆc<`++ีี$,4234&#264&"73#!"&546;732'52#4&462"U*X??X?ึjชD'€5K;ย(8((8€*๋>X??X—๋+@gK5);:((:'+๋๋ $064632"264&"'535332#!"&=33##5#5ั(((:X??X>+@•'Dซ+@@+@ธ:(((&>X??X‚@@+ึ€@@+@@++Uีซ(7/7/3#!"&546;3'33'33i,,,,e;;;;ซUช*@**+@+++@,,,;;;+ี@@@@@@@ภภ"/?/?3#!"&546;#!::::k,,,,*ีภภ+:::*,,,,ภ*+ึk๋•73''72#!"&5463#3#3#๋ีD6&ต j**V++•ZD.ผ  ึ*ึ@ภ๋?''%2'&5463ีภขL*ญญซภขLึ์tt++ี %7'#"3537"/&4?62+JJk *VคภภภหJK5 U@ภภภ+เ%-5=$264&"62"&4'#5'&54?6323"/264&"62"&4$"&462v>,,>+Z>>Z=E/*E <) ,?-ฮ>++>,Z==Z>\""K+>,,>Š>Z==Z^1„j< < ) +-๘+>,,>Š>Z==Zษ""U@ซี 15!264&"264&"'5462+"&=#+"&=&€าฎ+XฆX  ช  kk€ี3""3ี&  &@@ภ• -!'#264&"264&"%+"&=!+"&=76;2k* ๊ื),   ,๊`สย€ซ  ซ€%๋"575'&?546;5332#"'"'#3#"''+532727€€€ิ(@€@(1%%`%%1V**-)UU)-**.('\'(€U**U๋Ž c@@c Ž****++,,+U@ซี )5#264&"'5#264&"2#!57"&=4€k=Uk-ฆX, ,kk€mkk€-"3ห+ +ห3U@ซ๋ 5!264&"'5462#!57"&€o""€XฆX, ,+jj–""เ3##3เ+ +U@ซี )5#264&"'5#264&"2#!57"&=4€k=Uk-ฆX, ,kk€mkk€-"3ห+ +ห3€•เ!3735'735"/&#"#3576"&462ั<-',+- .G> o+&`""Bำซ+€ +@5*4"/dHI""++ภี''575575462ู็ซ+KJ*ชชซ@•5u  u5*ku  ukU๋•2#5!#335"&462•#3+€++ซ<4&&4&k3#ภ@@@ภ–€&4&&4@?ภี %.'77'7 ‘&ภภ& #ภภ#ซq••p?{••+?ี๋''77'.'7''7'7FPjภ#K- ‘&EZ•;จ>ง๋pQR•{;"q6Z.จ0๋++ภี%''575575462ภซ+KJ*ชชซซ5u  u5*ku  uk+Uีซ3%!2#!"&54635#535#"&=46;533#32+ซชVช–+U@ *+U@ €+๊* @ * @ +Uีซ &%'7/763#!"&=2654ᕗ!2"LFZ!![GLซชVšW:TT:W1F"UUUU@@ภภ37)3!535'5!Ÿย&๒Gซkkซ€k*ภj++jภ++@ีภ75!5#72++"&=+€+++2#€#3@++@@k@@#33#ี@ภ๏ -8CN7!'#264&"264&"%+"&=!+"&=76;2&"&546?"&546?"&546?k* ๊ื),   ,๊๋ Y X ๋`หร€ช  ช€*  %   %   % +Uีซ !5##5#3'5#3#35#573#5##35!U+U@**@+ี@ชVช@*k++@++@@@๊VV@@+ภี7!2654&/!+"&'‡ เ m4& @€€+ึUVV๊&:H+D{@+ภ๋17="26447&546325462632#"'"&=#"&"&52463,,พ ,   ,  ‰PpPppPp‹ ,,F!!   !!  ไqOqOOqOqU@ตภ 5264&"5#%"&=##46;23226=#"&547'7w  k€&, ึ€  "-+   jj;หk U•` š$-+ภี#+$2"&43!2+;!"&54?'#2"&4Z""ีF< L Ÿ๗M+o""€""o* Š#+ 5ขี""@@ภภ %5#5##33572#!"&5463€UVUUVjึีVUUVUU๋ึ*U๋•2#5!#335"&462•#3+€++ซ<4&&4&k3#ภ@@@ภ–€&4&&4U+ซี'-6264&""2646"26472#!"&54637"หjKKjK  3  •Dx2FUKjKKj    7ชVฤyG2@ภี"&46263"44&&4&@PpoQQopU&4&&4qK๊LL๊@+ภ๋ '6265#"&5#6"342#!"&546;462ิX?+&4&+…4&€Uึ*?X?๋>,&&,—&,??,U@ซภ +5#5#5#'5#5#5#3#5##5##33533€+++++ช++++++++ช++++ช+@++U**V++ซ++U**V+++€++++€+++++ีี264&""/&=46;2ht – $ ภ – ku $ – ภ – €@•ภ264&+72+#E@5KK5@U"VซKjK€€@@ภ๋ %5#5##3357!57'5!7U@*@@*ซ++€++2ี+@@+@@ภ*€€++€€*VC@@ภภ7632#"&546;20]/ $( –ี K  ]0/  K ี– ($ @+ภี6264&"&264&"&2 ๏""@"""ๆMภภภ""ข""@UซU+Uีซ &%'7/763#!"&=2654ᕗ!2"LFZ!![GLซชVšW:TT:W1F"UUUU+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ+@ีภ !5264&"5#72#!5#5463€   +ช๊&UU&ภUUภ  กjj๊&€UU€&::ึม''7&67>'&47=“““ะ L. @จZ– “““ะ@ .MZF•+Uีี#6264&"332#!"&546;462"ิX??X?+€'DชD#(8((8•?X??X*ว8((8(U๋ซ )$264&"7#3264&"%#"&5#"&5#5463!r@5_จK@+&4&€&4&++uญ5‹อUk&&&&๋V@@ภภ 1!'#264&"264&"%+"&=!+"&=76;5332k* ๊ื),   ,5€5`สย€ซ  ซ€++@ภี"%54&"6"26472+'#"&5463€XPX˜0""0"[U@@Uซ%%ไ"0!!0hี@@+@@ภภ%5'2'"54?7@€๕ x€r x€rk-( พ)-, B)-,๋๋'6264&"%3##5.'#53>7532"&4ย|WW|WT,,`C*C`,,`C*C`F22F2kW|WW|S*C`,,`C*C`,,`2F22F`@ ี'' ‘‘ีz@@k+•ี 7!!3264&".5462k*ึj"‘@ +GKjKU*;""++u%%4|+5KKk+•ี264&"&2.54๊,, |W,+4'  ,,ซW>PF=EAR>++ีี %5##376/&%2#!463€u+`5“&“+ีUี++“&“หU€::ึม''7&67>'&47=“““ะ L. @จZ– “““ะ@ .MZF•@@ภภ !7!'/265##526572#!"&5463k*`J6J>W+>,&๊ึ€€`@ X>,?kA'*ึ*@Uภซ%5#%##5##5#57!'!5€@+UึVช€UUU€€€€+kkซ++€๋€!77'+ภ*€`"<€ซ€QU@ซภ I2654&"264&#"264&#"73+"&=.535.535.53546;23๎$$$$ึ$@$ ช $@$@$@ ช @$@…""„""ง-, ,-, ,>•เ"7'77#5726323"'#5'6"&462ำ•i"'*o ?G. -+-I""c+ญId/"3+5@* €*ห""k+•ี 5#5##335&2.54U@*@@*S|W,+4' +*@@*@@ชW>PF=EAR>k+•ี 6/&7'62.54>PGG|W,+4' _PGGีW>PF=EAR>@@ภภ#/5ภก8’ภ€’8k+•ี"6274&"6"2654&2.54ษn;4;f""i|W,+4' ี.จDW>PF=EAR>@@ภภ %#7'7#57'53'73''7ภ€1>=ฯ€1=>O€1>=ฯ€1=>ภ€1=>O€1>=ฯ€1=>O€1>=@+ภี463#5#'53#5.=3353UA*56j*."5".+*+€7Vชk••"1ภภ1"••••U@ตภ 7?#536264&"7"&=##46;23226=#"&547'7ซU+U+ฬ  ;, ึ€  "-€•k K  /หk U•` š$-@@๋๋ &546;462"632+54๕)ั š ?X??XH4@!•€9)ั *NX??X?G' u++ีี"&.6%54&#"337335'26!467623#462"6462"€B>;E!$ < !+.V.++ขปีี  ‰  ญ“&&“! !.D-่-D•k3    U@ซี 0$264&"'35#5#264&"2##'##57"&=4>R+kk*k€SX, +*Q*0 ,55•ƒUUUU–-"3ห+ ++ +ห$k+•ี *%5#264&"7#3#'##57.=46?#53#kึ]ต +*Q*0" D;fึF?Aีkk`& ** "$ด) *+ีเ!(/3735'735"&/&#"3576"&46255#573#'7{;-%.+, -G1  p*&a""ซ55u5uu55Bำซ+€ก,@7*".dGI""ท%56& Z %56 UUซซ #%53'53'5373#53#535335353UVVVึV*VV€VึVVV*VึVUVV€VV€VVVV€VVVV€VVVVVVUUซซ!'7ซwซซw*xซซx•ภk+3•ึk+k++ีี %7#&2"&4Uชฐ}}ฐ}ีVช}ฐ}}ฐ•ีk@?•kkีkkUUซซ'7!5!'ซซwwซซซx*x++ีี %'7''72"&4kMMMMMMMMฅฐ}}ฐ}ณMMMMMMMM@}ฐ}}ฐIkภ‰?'7ภโwงโwซ€I€'7Ibb€€bbb€€ท€U€'7'ี€€bb€€€bbkk•• ''7'77•wwwwwwwwwwwwwwwww€ท€U''€bbU€bb€ซ€I'7b€€bI€€bkk•• 3#5#53#5'53#3#5+j*@@*jภj@@j•j@ึ@j*–j*@V@*jkk•• 3#5353#'53#553#5U@j**j@ช*jj*U*jึj*@๊@j*ช*j@@€ภ€ !!5!5!@€€€€€€+j**k++Uีซ+2"&462"&4&2"&4๏""š""ๆ""+""""""ีU+ซ62"&462"&46"&462๏""""<""ซ""š""D""VUซซ7#7&#"32673#"&4632y2–E&45KK5*B ,\;FddFGy2–E&KjK/&8HdŽdžUbซ '777'bbbDDฆbbDDbbDDศbbDDž@bภ %7'?'7DbbDDbb|DbbฤDbbUUซซ7'#Uซซw*xซซwwk@ซซ 353#'๋Mฤ+๏M€@MึM€U@•ซ %'7#33'7•€M๏+ฤMภ€MึMUUซซ'737ซซซw*xซซww€€‰€ 3#%'7€++ €€b€€€bw€€€ 3#'7'7U++€€b€โ€€bk•แ'264&"264&"7!547'76275!"&7  t  ญ=ึ=-1 D 1ๆ*W|W@    W-KK--11UU>XX@+ภี!%'73#5'7'753'64'7((<\\zbwwbฬ!V1 ค)QQ)\\yขbwwbขd2z4*X*V1+Uีซ6264&"72#"&463#53ฤ""*GddGFdd***ี""ผeŒedŽd@kU**++ีี%2654''7&#"62"&4Fe%๏0r%๏09FeSฐ}}ฐ}UeF90๏%ซ90๏%e}ฐ}}ฐ++ีี%654&#"27'2"&4‡$eF<-i<-๐$eฐ}}ฐ}—-Pp –ี K  //^/ $&+?,>W+pPK K ี– ($ /\1/  @@ภ๋:54&"32+"&=46354622#"&546;276šZ k , –ี K  //^/ $ซ  U U   K ี– ($ /\1/  Cภ(%"'&'&=&""/&476 %#53#7๚5 .h. 5i"๚ €K`€•œ5BB5d‹K€ `€–@@ภภ $3#2#"&546;276#5•++ –ี K  //^/ $+ภ•v K ี– ($ /\1/   ••U+ซี 5##5##5#72#!"&57€+++ซUVVVVVV€ช€U+ซี%5#5#2#!"&57***•๋jjV++@ช€++ีี 5##5##5#%2#!463k++*++ีU++++++ภU€++ีี5#5#2#!463***ภีU+UUV++U€Uซ๋%55"&5472'654&#'7UUFeK5FeK5UU€@UV@eF2)!5K+eF2)!5K@UV==ซซ #)'654'57'567'7#7&54?'67ซ33 &/๎O2 ฌ&/€33f ซ33E0*4&/€ฐ2,ฌ4&/€33E0* @Uภซ%75375>54'553'4677#7&๋*ซ22H8&/%0U*ีH8&/%0€22๋€€ภ32F;\, B*5%/€๊++k;\, B*5%/€32k•๋ %'353#2+"&5463UUU@*Vึึึ๋VVjj€*ึ€€+•๋!)2+&'3#&'54632#4&#2#52#4&#k-Aึja‰*qO&@>W+>,๊•+*€ ๋‰bOq€&–X>,?@Uภซ -!'#264&"264&"%+"&=!+"&=76;2k* ๊ื),   ,๊+`หร€ช  ช€@ภ#%#2+"&54635373#53'53Uชตภ๕+++k+k+k*ึUภ@ีึึซ€+ึึ+€€++ีี%55#3572#!463€Uซซ€ีUีซDDซEปU€๋๋ /7M75"&='73#"&54632++3232654754&"32+"&=4635462ีgV+}XY}}Y!+ +€  ,/Y j ,A*fAaทY}}YX} 6* * @/Dฒ  U U  *€ีk.>!##5#%!532ซ&4&&4&ช€ช€ช๋ภ#2&&4&&p*++j*€3 k็ฅ.>7'#5%%7ด0303ฅ•aช0๘-ถ"1030g(’(""`(_yB @•๋k 2!5335"&462•#3*+ซ<4&&4&k3#€ึ––€&4&&4+@๎ภ%'#"&=33276%;#"&=3็ OI•&€K H t&€€,>* $•&ซ€• e&+?,ภ@@ีภ%2+5#"&=332%;#"&=3ต  `•&€kึ&€€,?+€•&ซ€•€&+?,ภ@@ญภ !;#"&=3+57#"&=33232k&UU,?+?`€&€k* &+?,ภฆ@U&ซ€•+@ีุ!*%'#"&/&67367'#"&/33.>Z{ Q’##040>c–(<***$?#ภ` @~  % ."WU+3'ัส# #U5ซึ"*%'#"&=46;23"'3;#"&=36&462ซKl&9>8Jฟ&€€,?+/"TK&{!//O&+?,ภ""+Uีซ (5#5#5#7"3#!"&=2654ᕗ!2*****๊ชVK**`**`** "UUU+๋ี!2#!"&546;'77ภ••€€€ขFUUF+VU++FUUF@๋ภ5!2+#5#"&5463U•€€kชkUซึ+++@๋ภ%!2+#5#"&5463ภ€€kชk•+++€@€ภ2#5'546;53353VKjK*V*kuK@@KuUUUU+ภี)"&462"&462#5#76;2#5#546;2#r$$ู$$ต@@6  6@๕ @ €$$$$’€ขข€ uu U๋ก 762&"62'6 &"k>ฏ=*,~,+L@๋ba+PเP๋==+,,+@๋aa+OOU+ซ๋ +%5#5##335'354&"2#!"&=46;5462U@*@@*W„'6'ย?X?ซ*@@*@@ี++''Fึึ+,??,+U๋ช (763&'77&'7!>&%2#"&54?6k@\1%ึ  +'1 E3*;›O=w- 4o๋@=% >*'<3;/9)-€ ํ ๘@ภ๋#'>3232'354&"'#!"&=47'7พ';),?t'6',€'(6?,+ฒ+''”ึ +U+ีี +5#535#264&"73#"&5#"&=35#'732k@@ซซUI  ึ*ช&4&+–V@@๋ี@@++ึ@@€  3*&&@@*@@+ีี73'#''#"&547'#'632'3•J+e ;6CX}&<VJš6CX}&c๋*ป`<&}XC6;Ÿš&}XC6d++ีี %5#62"&4kึฐ}}ฐ}๋**๊}ฐ}}ฐี@+ภ 3#462"ีVV$$ภg$$++ีี %3#'.46ฟlLLlฟ+Qoo๊LlชlLฟVzฆz++ีี %>7#&73.2"&4:S”ภV@?i”Sงฐ}}ฐ}WS:V‚`R”:S3}ฐ}}ฐUUซซ462"462"&4632#";V<K$$@&! .. .. !&++• b b พ ??  +@ีภ#'+/39%#57#55#3#3#5#5#5#5#5#5#5#5#3!3€+++Vซ+++++*******++++++++ซีVีภ++U**ชี+*++***V++U++U****V++U++U**ี€k๋• $%2#54'6"2!54>"&462"&462UB8€*น8B7ี8x4&&4%…4&&4&๋$55,$55$:&4&&4&&4&&4€•#/62!54%#54&"&462"'64'632'##5#5353ํPX%8@‘4&&4&+  &&๏@+@@+๋#++++"H&4&&4&D&4&+@@+@@ @@ภี #,%5#5#5#5#5#5#'5#5#5#73!357•***V*******V*****ี€€€@@ภ++U****V++U++U**ช++U++U**ชี+*@@++ีี %$"&'3&"&462"&462264&"2"&4%J; ฺ žƒฑŒeeŒeSฐ}}ฐ}‹)!!aำeŒeeŒ}ฐ}}ฐ++ีี %62#>"&462"&462264&"2"&4J; ฺ "ƒฑŒeeŒeSฐ}}ฐ}ี)!!iำeŒeeŒ}ฐ}}ฐU+ซห%!5754675462"&53€+ช+3--3€Vซ++j2J    J2๊U+ซห!%54&"7!5754675462"&53U.N.ี+ช+3--3o"V•€)77)€++j2J    J2๊U+ภห'%'>7372635462"&53'!57547'7€ฟ -3n$V„ห6++<วษ   J2๊:ะ9++k) ;++ีห%$"&537!5754675462&'7%#67$UV+ช+3--3*FSอG+S+าj++j2J    J'V3@g‰2Wg@U+ซห $5#335#!5754675462"&535j;;j;†+ช+3--3€V/&&I&&;++j2J    J2๊@@ภภ#2#75'3+5'7#"&=46;'#•kVVk€jV€€Vkภ€VkีV€k@Vk€ชkV+Uีี /%2654'#+7"3&546;&72#!"&546;73,?-&U!4,?-&U!wชD'€'•?,&+ึ?,&+@**k๋• $%2#54'6"2!54>"&462"&462UB8€*น8B7ี8x4&&4%…4&&4&๋$55,$55$:&4&&4&&4&&4k๋•)1>"264"&462&"264"&46254&#"#54&"%2!546326q"" >,,>,๚"" >,,>,เG$& GHG++`*`+/11u""\,>++>I""\,>++>ก    `'::'UUซซ62!546"&462สluชฮF22F2ี/&++&Z2F33FU๋ซ$2!54'3##5#5353"&462 luช@@+@@+ใF22F2ี/&++&…+@@+@k2F33FUUซซ !62!54>2"&4"!54&"264เ@L?ช?IF22F2XVo&&๋*@@*า3F22Fถ!  แ&&U€••7#53##5#5355`*6`VV*VV~๋โ U+UU+U@@ภภ %5##5##5#2#!"&5463k++*++ึ•VVึึ––+ึ*++ีี"*%654&'++32325"&='2"&4~-;0* *€ ˆfWฐ}}ฐ}0C5V + + @T)fA`v}ฐ}}ฐ@๋ภ #5'7'๋+ภ๋V••••ภ€ซ”i€YRRVQQ@,ภี&%2"&547'#"&46327&5462#"'6€$%2%—&&–&4&&–˜ฉ%%% X&4&W &&4&X XU+ซ๒ 72654'"&54732654&'๚+; D<(C@KdŽdE,! )k;+,*) 5'‡4•TFddFlR!.-"4++ีี !)62#&"#6264&"2"&4462"6462"J; #b# ŒeeŒeSฐ}}ฐ}jƒี)!**!WeŒeeŒ}ฐ}}ฐ0++ีี#6264&"2"&4462"6462"3#บŒeeŒeSฐ}}ฐ}jƒ~€€UeŒeeŒ}ฐ}}ฐ0@ ++ีี !)6273"&'3264&"2"&4462"6462"ฯb# ;J; #ŒeeŒeSฐ}}ฐ}jƒซ*!))!€eŒeeŒ}ฐ}}ฐ0++ีี%-62#67'7'77'7''7'7264&"2"&4J; ฺ œ‰ŒeeŒeSฐ}}ฐ}ี)!!TC๏eŒeeŒ}ฐ}}ฐ++ีี !$"&'3''7?'264&"2"&4%J; ฺ ฃ-.A.-rŒeeŒeSฐ}}ฐ}‹)!!x----ภeŒeeŒ}ฐ}}ฐ@@ภภ?''%2#!"&5463ีภขL*ึ•ภขLภึ*@@ภภ2#!"&5463!!•ึ*ึ*ภึ*+ึ++ีี6264&"2"&4บŒeeŒeSฐ}}ฐ}UeŒeeŒ}ฐ}}ฐ++ีี6264&"2"&462"&4บŒeeŒeSฐ}}ฐ}ฉX??X?UeŒeeŒ}ฐ}}ฐ?X??X+@ีี %7'?„#t™<<™t#P–e  e–+@ีี%'7/'7'?PG^$ีt#„„#t™<<ท0[>VCe–PP–e +@ีี %'7/%'7'?PG^$$^G%t#„„#t™<<ท0[>VV>[ดe–PP–e  DN2#.''4+32765'2+5#"&53324+5324#"#476327"&'3d“ E5Qo/$ /5#11 #HQd“ D‡c;`Q๛9{"Y/ &ชS    62  ูQˆc<`@+ภี ##5###5!&2"&4ภ€+*+€€ั""@๋€€+j""++ภ๋ !53#5!3#'3#๕หk*@@ึ•๋@@€@@๋k++U–j@@–––@@ีภ %$264&"53#!"&5463!2#"3H5ีึ*ภเGชช+*ช@@ภภ7!54&"64&"2'463!2#!"&5€XPXภ&4&&4ฺ*ึ•%%t4&&4&•ึ++ีี627."6"264&2"&4ฏข/XNXš4&&4&˜ฐ}}ฐ}fE&%๊&4&&4f}ฐ}}ฐ+ร๋%-97;!"&54?'#53367+2"&4&2"&475#53533#™๗M+F$ –K%R Ÿภ""ผ""€@@*@@ล+ 5ข**+ Mˆ•#H""""ฺ@+@@+@++ีุ6264&"62"&47'5''7'7ย|WW|WE pp pหUeCbbHbUX|WW|qžqqžp2<€cR RR!S ++ีุ #3##5#535264&"62"&4%'7'7@@*@@)|WW|WE pp p•bหbb@@+@@+@๋X|WW|qžqqžL!S R R#+ีุ !1'7'32'#"&547''7''7"'632'654&ซาา"W>4๖L%/7GPp/ฒbs!**Pp! Wบ า*5>XzL่%//qOF7.!S X qO,( >W++ีุ ?'7264&"62"&47'7'7แi€D|WW|WE pp phbbHbสj€DขX|WW|qžqqž~R RR!S +ี&2L5##5#7!47'&6632762"&=4$2"&=45!+"&=#"&=#"&@V‹54  AฉU * •=&AB%  ™ ••  •• ยีี KKKK ++ีี%5#75#72#!463***ภีUภ++U€€ภU€@๋ภ%!2#!"&5463#53#535ภ€€€U*jภj@j,ิVึ*€@k+@k+@@@ภภ %5##5##5#2#!"&5463k++*++ึ•VVึึ––+ึ*@@ภ๋ )5#5#5#"26472#!"&546;>2kึึึ––t  €ึY * @++U**V+++   ึ*@@ภ๋-%54&""264&"26472#!"&546;>2€XPXš4&&4&7  €ึY * k%%&4&&4{   ึ*@@ภ๋ %264&"5#5#2#!"&546;>2๗  ****ชึY * •  อ€€U++@ึ*@@ภ๋$%5#55"26472#!"&546;>2UUkk   €ึY * ภU@jk@   ึ*@@ภ๋$%7#5##6"26472#!"&546;>2k@V@t  €ึY * €kUUี   ึ*@@ภ๋ #?''6"26472#!"&546;>2ีซ7‰  €ึY * •ซŒ7ี   ึ*Uซ๋#'72654/"&54635eFUU5Kq5KeFUU[)2Fe@VU@K5DK5!)2Fe@VUUซ%3'337#!"&5467>32+@kk@Vr):?,๋5KB0L-:X๋jjV•=*,?K51I'0IU+ซี7572#!"&5463€56•ซซ ซ*ชVk@•ภ 2'463k••ภซ@@Uk@•ภ%#72'463kึkk••€๋/ซ@@UU@ซภ75#5#7#3#3#"&'#53&=#53547#5367'7627+VVVึ-++-<;F;<-++-<#//#++U**ช*++##++*#..# ๋๎%/&'.77'6ไ1ย$Q ^@\&X k1ย X&\@\ Q$U๋ซ3'34632&#"%##"'73265#€@UV@eF2)!5KV@eF2)!5K@UUFeK UFeK5+Uีซ!!ีVีˆซชฺ++ีี ?''62"&4ีภขL=ฐ}}ฐ}•ภขLี}ฐ}}ฐ@๋ซ%#2#!"&54633#53#3#ภภภ€ี––––––k๋@๋เ Š  U+ซี7572#!"&5463€56•ซซ ซ*ชV+€ี€ %7'7''77cc€€Œ€€cžbb€€€€b+Uีซ5!5!2#!"&5463ซชVชVชU++ี€€+@@ภภ 3#53!53'53ซซซ€ซซซภ€ีี€€ซีีk@•ภ!5373!+"&•ึJjหชซ++ภU+ซี 3'5#5##!"&5463vv@ชชช€€@uเ++U++U€V@@ภภ/264&"%2#!"&=463264&"%2#!"&=463„""@ ช /""@ ช @""f € € ซ""g € € Ikภ‰?'7ภโwฆใw k๙‰ ?'77'7 wZxY‡‡‡โwwYฤˆˆ@@ภ๋%5!332#!"&546;533#5•ึ๊+ึ+ชkk๊๊€+ึ*++ภkk@@ภภ 2#!"&=3!!#54637#53'7•ึ+*ึ+l7ฮฮ7kkภึUU*UU๓8*8kk++ีี %72"&42"&4/QฏQ(ฐ}}ฐ}ห  ัฏQฏU}ฐ}}ฐA  ++๋๋/2++54&"#"&=3264&+546;546232ต  Q"0"Q !! V,V,V !! Q"0"Q   V++ีี&62654'#"'2"&42"&4&2"&4บŒen@!OSฐ}}ฐ} pUeFZP# F}ฐ}}ฐS+9ีภ%'.5463263250;C2:&&:2C4=E90.D71D--D1'T>>+9ีภ-%>54&#"#.#"2'.5463260.6+ +(+ +6.0`2C;05E=4C2:&&t+,<. ** .<,+ND17D.0>>T'1D--++ีี5#5#2#!463***ภีU+UUV++U€U+ซี!6462"'654&"327#!"&546;ภ&4&&4ลR?X??,^ ซ€ั4&&4&MR,>>X?_V€U6สซ!%'#"'5332673'"#>327#7&cgh(0>,,€6-'9+':+T8=,,€6ฝhg,,€60%$ค0%6J,,€6 @@ภภ"&+/48<%5353!!"&553'5353"&53#532##5#46#57#5@+++U+++++U*ช+€*+++++•++++*+ึ+ซ++U**V++ี+++ซ**V++ @@ภภ #(,075335375#2+"&=4635353"&53'53'53•++*€ีีี€+ี++++++@++++€ีีีี€++++*++V**kU•ภ 7!!%'353k*ึ*••U€€+๋••€€+@ีี %7'?„#t™<<™t#P–e  e–++ีี$264&"&264&"264&"2"&4?, ,U,,U,, 3ฐ}}ฐ}‹,,ม,, ,,+}ฐ}}ฐ++ีี!654&"34623475#2"&4A2F2*" **Cฐ}}ฐ}#22#" ! !‘**j}ฐ}}ฐ++ีี6264&"2"&4''7'7บŒeeŒeSฐ}}ฐ} 7777777UeŒeeŒ}ฐ}}ฐ7777777@ีภ3'&2"'73264&"3/34 K[:žqqž8,=>XX|W@VS@UZ-7ีp p8,W|WW>VSP+Uีภ 7#5#7##5#ีj@ีี@jVUซภภซ€€+€ี 75#'3!!57'UชชUUชีUUUU UKKภUUK€€UU€€UU€+€ี !!57'€UUUUี€UU€€UUU+ซ๋'54&"264&"72#!"&=46;5462B'6'1""ซ?X?U+''+ภ""ฆึึ+,??,+++ีี5#5#2"&4***Cฐ}}ฐ}@++ซ€€@}ฐ}}ฐ++ีี 53264&"2"&453๋*[ŒeeŒeSฐ}}ฐ}ภ*@++๋eŒeeŒ}ฐ}}ฐร€€@๋ภ75#53572#!"&=3!!#5463๋ึึU€€+€€+ซ@*@UภีVV,VUU4ซะ%7"&4?Z&Lญ2dŽd2y^5Z&54L๙2dd2y@kี•+"&=46;2x]] ๊๊ƒƒƒึ@kี•%7'#%+"&=46;2ULL๊ ]] ๊๊•kkึ๎ƒƒึ ++ีี #+05=%364'#67#'64'#67#'67&/3&47#73&&'&2"&4]HH)= ? d2 R 9 = = PHHซ R x = ญฐ}}ฐ}ี,‹8(R,,),,ึ($8ช8$(*,น),,,8$(€}ฐ}}ฐ@@ภภ3#5'7#53#!"&546;#+•+ััLj+ึ••ภ•Lััึ••*+ึ@•ภk !!5!%5!%5353'53•+ี+ี+€+++++k+ซ++V**U++ซ++V**U+ซ๋'54&"264&"72#!"&=46;5462B'6'1""ซ?X?U+''+ภ""ฆึึ+,??,+U+ซ๋!)%5!2#!"&=46;54&"#462"&462€ย'6')?X?Z""Uึึึึ+'',??,+ภ""U+ซ๋ #+%5!354&"2#!"&=46;5462"&462€>„'6'ย?X?Z""Uึึ+++''Fึึ+,??,+ภ""++ีี*%64&#"'&#"264&""/&=46;2p[ญt – $ ภ – บ,[ u $ – ภ – ++ี2#!"&546;53#35ซช+ซ€*€€Uซ€U+ซี3'5#5##335#!"&5463vv@@*@@*€@u๖*@@*@@*€V@Uภซ##5#72+535!3#"&5463U@*@๊UUึUU+V€€ึ+ีี+@@ภภ3#5'7#53#!"&546;#+•+ััLj+ึ••ภ•Lััึ••*+ึ๋๋ %3'35%5#535#'775#7#+@kk@k@@ภ@kkU@kk@ภ@kk@@k@V@@V@kk@@kk@+Uีซ %%7'654&"32?2#!"&54632"&4f>8P88(ƒชŠ, ,|>(88P8๑k, ,+Uีซ5!5!2#!"&5463ซชVชVชU++ี€€++@ีภ /%54&"2672+5>5#"&5##"&546;73+""€–->+2F2+>-–D'€'๋UUป-G/#33#/G-++@@ภ๋'%54&""26472#!"&546;53353€XPXš4&&4&Uึ+ช+€%%&4&&4fึ*++++CM$264&"7/+"5'&'/&?4&465'&?66?4;276'"#&ˆo*  *OB^๗ซK%  %%  %ช^Bซ๗k•๋%#2+"&5463#57#5kึึึ€***k*ึ€€ึ€€V++UUซซ !62!54>2"&4"!54&"264เ@L?ช?IF22F2XVo&&๋*@@*า3F22Fถ!  แ&&+ี7!''72#!"&546;!!"&5•+K5Kเซ€+€€ภ`@`kี*+ี*+@@ภภ!3#2#"&546;276ภ€@ซ –ี K  /0]/ $ภ•@6 K ี– ($ /]0/  +ภ 35#5#2 6๋****ˆxvU+ี€€[ฦ;Z@๋ภ%!2#!"&5463#5ภ€€€Uชj,ิVึ*U€€ U๕ซ #7'#'73•``U`8จU``U`8จซซซซc๒ซซซc@@ภภ"&5472654/#5|Dp pD7W|W7I*’:XPppPX:-G>WW>G,Mีี+@ีภ !5264&"5#72#!5#5463€   +ช๊&UU&ภUUภ  กjj๊&€UU€&++ีี '5264&"2"&4 `p1ŒeeŒeSฐ}}ฐ}kp9D€๊eŒeeŒ}ฐ}}ฐ++ีี +463!22'#"&=!5k ีU  U U๋  V+ @ ภU +ภ@+ภี!%7777777''''''%5!5!5!@  +ช V ๕++U**V+++@ีี  A%5#&''7#5!"2646"2642#!"&=46;&546327632ซm-#@@#-mVชt  t  Vช/&! !&ี€<W W<€j**@    ๊๊&&@๋ี %5#5#***ึ๋๋ีVVU++@•k@ีภ3'&2"'73264&"3/34 K[:žqqž8,=>XX|W@VS@UZ-7ีp p8,W|WW>VSPk+•ี264&"&2.54๊,, |W,+4'  ,,ซW>PF=EAR>++ีี '5264&"2"&4 `p1ŒeeŒeSฐ}}ฐ}kp9D€๊eŒeeŒ}ฐ}}ฐ@Kตภ6264&"'5'#"&462ฃP88P8เj j&4:QQtP!ี8P88P8j j!PtQQ:4&-+ำี?6264&"7/+"/&'/&?&47'&?66?6;276แ>,,>,๊-+ 5V5 +--+ 5V5 +-ต,>,,> # J8 8 J ## J8 8 J #@@ภภ7GO$4'76/&&/&+"'&?;2?67?6/72#!"&54632"&4p % < %  % < % &ึ„""๖4 '(34 '(3ฯึ*•""@ภภ2#"'73264&"3'34"&462ฐ ppPB3(/>WW|W@VU@๋""ภp p(W|WW>UUP?""kz%'73#5'7'75353#53353=((=\\zbwwb@+ึ++*ฯ(PP([\zขbxwbข++++++€€%#2+"&546353#53#53Uชชช•+€*€+ซUซU++++++@๋ภ)264&5'75373#'!2#!"&5463&&o 5 5 5 เ€€€@€&4&•5 5 5 5 A,ิVึ*u๏‹ '7'537#553''7{tt!]]o*V+ซ+]]!tt‹‹‹pp…********…pp‹‹"๋๋ (2#4&"#4''75&5462&2#4&"#4ŸยŠ+p p+I@@I ,s|W*?X?*๋ŠaPppPa’FI@@IF $$นW>,??,>๋๋ "-9E%53#5&3#5354623#53546253#5.753#5.3#535462k€+*V+€+  ซ+€*  U€*+ซ€*U*€+  ซ** ZZWU€€U ^€€U ึ**-ZZ ** ZZ ?U€€U ๋๋ "-9E%53#5&3#5354623#53546253#5.753#5.3#535462k€+*V+€+  ซ+€*  U€*+ซ€*U*€+  ซ** ZZWU€€U ^€€U ึ**-ZZ ** ZZ ?U€€U k+•ี 353353353#5'53546;2ซ***+@ช@ชซ@****@@€€@@€€@๋๋'3;$2"&462"&4264&"2"&42"&46+"&46;2"&462>=ฅ pp p_ยŠŠยŠ“ซ@@ƒภ}ุp pp ;ŠยŠŠยกิ}@๋ภ%!2#!"&5463/'%'#ภ€€€๋++U5555€+Vj,ิVึ*๋66€V++++ถ6@@ภภ $(3#2#"&546;276'#5##5•++ –ี K  /0]/ $++*@+` K ี– ($ /]0/  ‹++++Uซี#!53"&5472654/#553#53@+ JdŽdJ=KjK>-**€+++ก4XFddFX4&H5KK5H%Sีี+++++Zฆ(2&"'662&"264&"72+"&5463cC8 8E ,|,Z;""k € E88E,,ก""f  k•๋#'#5.5326553#536"&=46253•K5*5K$C\C1+€*4&&4&ซ++6RFFR6/>>/ี++++๋&€&&€๏+++@ีี?'7353#!"&53546;2ภ  V*€ช€V€kUk+++๋++๋๋%%7'5353#!"&=3546;2!#!"&=uuU+kีjUภUีภU@k+++๊๊+€๋๋@๋ี "6264&"'3'32#!"/&546;762๏""€@of ( ๊ 6 f]•""‘^^ (‘ฦ Œ +ภี#+$2"&43!2+;!"&54?'#2"&4Z""ีF< L Ÿ๗M+o""€""o* Š#+ 5ขี""++ีี %5#5#5#'5#5#5#2#!463€ซซซkk*++++++ีUU++@++@++€++@++@++U€4 ๋ภ'7'3''##3อหlN—X,Ux-m(m หmOนvเ@@๋++ีี %'7/72"&4ZPi))iPZXฐ}}ฐ}€gE `aEg6}ฐ}}ฐ@Uภซ%5#%##5##5#57!'!5€@+UึVช€UUU€€€€+kkซ++Ukซ• !!5!5!5#5UVชVชึึ•*ซ++€++ซ**+kี• "72#54>2#54&"&462"&462ภ3•7B—JP๊4&&4&v,,๋.05$ 00`&4&&4;, ,@kภ• 5#5353#'ภU––ึ––U@U@*@€@*@Uk@•ภ ##5#3'353ภU@*@๊@UU@*ภU––ึUU–++ีี %#5##'3353'&2"&4u5+5K 5+5K-ฐ}}ฐ}ภUUKหUUKJ}ฐ}}ฐ5๋ต2#!"&546;#!#5'3533ภ€€€€€@U@*@ตี+*ี+*๋Uภภ@๋ภ%5#5#2#!"&5463ภซี€€kีUึUึ*@๋ภ  ',049=A%53#5375353535353532#5"&53'535346353'53k*€+€+++š***++ี++ึี+++€+ึ++++@++++ซ**ซ+U++ซ++U+++++U€€*++U++U+ช**U++U@ซภ +5#5#5#'5#5#5#3#5##5##33533€+++++ช++++++++ช++++ช+@++U**V++ซ++U**V+++€++++€+++๋ภ3#2'&=7#"&57#54?63•VVU Œ ‡A ภี  b)–@๋๋3+"&=4?323๋A ภ Œ ‡*V+)–ี  b/2'&5>7#"&=4?63'+"&=4?32เ j o 1 P1  j o + ‹ i 4 qUq ‹ i 4 @•ภk %53'3#53!5!%5!%5!•+++++€+ี+ี+๋**€+ซ++++V**U++@@ภ๋3#5!2#!"&546;53353•kkึ*ึ+ช++kU๊๊Uึ*++++U๋ซ .467264&"62"&4@/&8HH8&œjKKjK:ŒeeŒe*TB ,\v\, ์KjKKjเeŒeeŒ++ีี5"&46;"&54752654'7"&546752654'—>}ฐ}}X"$2F2&KjK=.?WeŒe2—>YX}}ฐ}ฐ  -,#22#!&45KK5/G+`AFeeFG2+๋ี #%3'7#'##''7&'367#53533#SE#`+e+`MBkm(+.๏–*–?5•]9@@l,Bjk,5(3?+**+P;+€ี€ %7''777U1hUž€U†1€1hUž€U†1€@ซีU5!5!5ีUภ@U@*@+€ี€ 3'''77U€1†U€žUh€€1†U€žUhk@•ภ 2'463k••ภซ@@Uk@•ภ%#72'463kึkk••€๋/ซ@@U@ภ๋?''.=ีซ7€ภnRRn•ซŒ7V€Y““Y€+@ภภ2#!"&=4632#!"&=463ซ • k • ภ € € ี € € U€ภ• 733#!3ซภ@@ี@€๋๋๋+kีซ 3#!533€UUซUึ€๋๋๋*@ภU€ภ• 3#!333Ukkkk•๋๋๋+@ภภ!!2#!"&=4635!+•k€ • •ภ@+ € € ๋@@Ukซ• !!5!5!%5!UVชVชVชV•*V++ช**U++Ukภ• !!5!%5!%5353'53ภ•VVVVV•UีUUjVVkUUีUUjVVU€ภ• 3+5353#53#53'53Ukk€kk๋k๋kkk•€€€•€€€€€€•€€U€ภ• 3#53!3353ี๋๋€k•kk•€•€€๋€€U€ภ•!!5!Uk•k•€•€€+kภ•/2+"&546;2+"&5463#2+"&5463 @ ึ @ ๋ @ •    `๋ 2"&4264&"62"&'6ๆ4&&4&X??X?ž€€ž€@&4&&4…?X??XฬXHHXXHH+๊ภ%532'327'#"&547'7.'#"&'67."'632'654&&Ÿ ?,!&—z5+2O€7 +า.'.O1>?@&3,? !&—†4XH>, ,7.XH;*>,?+@ีี  A%5#&''7#5!"2646"2642#!"&=46;&546327632ซm-#@@#-mVชt  t  Vช/&! !&ี€<W W<€j**@    ๊๊&&++ีี5!5!2+'5#"&=463ซชVชVVUUV+€€k++๋j**j๋+@ีี -%5##5##5#5!352#!"&=46;546;2ซ@+€+@Vชk€kช@€ี€****€j**@+++๊๊+++@ีี5#32#!"&=46;546;2+VึชVV€++๊๊+++Kีภ %'5'#"'73264&"3'34632kjk&4&! (88P8JXR5R9:Q"ีj k!8P88(UU9RQ:3'kk••!!!Žไ*ึ•ี+*+@ีภ#%/?264&"332#!"&546;::::GX??X?+€'DชD•;;;P?X>>X+++ีี#'2#4>54&"#4264&"2"&453F2@*"*ŒeeŒeSฐ}}ฐ}ภ*€2#9!#๙eŒeeŒ}ฐ}}ฐุ++@kภ• !!5!5!%5!@€€€€€€€•*V++ช**U++@Kตภ %##5#53533264&"'5'#"&462++++]P88P8เj j&4:QQtP!+++++k8P88P8j j!PtQQ:4&@Kตภ 3#264&"'5'#"&462•kkP88P8เj j&4:QQtP!@V8P88P8j j!PtQQ:4&ภ๋@+5#72+#553##5#53##553#5##53ห++  + u` –` U +  5  +€ `` `` +€55€++@ีภ %#546;23#%3#5!#5#kึ€ภ@@j@@ภV@ึ๋ชj@@@ซ€€@@5@หี%&/55'!!+R|"j);q  ˜–jฬ n2ฐ ภ C+'@ฺด&'77'776!!ิ |Rq"1*jX)“r x–j?! T ™ ‰โ+€U€• 732653"&733'3€+2F2+KjKk*K``Kี#22#5KK๕w``kภ•@ ##3##53#2#353+"&=46;#•@++ `ี J* @ u   +€ @ + V €@@ภภ%5#%2#!"&5463kึึ๋**ีึ*++ีี ?''5#2"&4q)ึึฐ}}ฐ}ีq)œ++U}ฐ}}ฐZRฏง$64&""&4653#5!#35DDaDDnXX|VVlUUUUUˆDaDDaD X|VV|X@UซUUUU++ีี/6264&"2"&4"32653#"&=47632#4'&'&บŒeeŒeSฐ}}ฐ}า((&(*&!& UeŒeeŒ}ฐ}}ฐ:: 0*)  >+ฤี6Je|%#&'&54622654&#"'&47>32"&54&"'3262#"'&5432"'&'&5462"54&""&76762'&'.%"'&#"&47632>.!.&6'&[@.L W4Ig'6&&(* =7)  (3 9GdG:R: Š 02x21 -.l.-,;N,1 1,N>ย @๋ภ%!%#!"&5463!2#5ภ€ซ€€Vชj,ิ+€€+ี $(83'7'3?2#5!!#3#535#"&54635#72+"&=463A5455Aภ*€++ซ+•ภkk k @&>''>&@Ukk++**+€––ภ ภ ภ eฅภ,%+"/76323546232'&546254&"’ ‘ j Iq+8P8* ,ญ q  jๅ€24(88(5Pk+•ี-5%3#"&5467326'4633"'32#5#"&5<62",:',>0%&!6*$282@*k$$€%0>,':,!&ั '*)Iujู$$+kี• %'73#55#5@UU••€UU•๋UU@*V@UU@*@+ภี#'%5!2#!"&546;53353#5##5##5•ึ*ึ+ช+++*++U๋๋Vี+****–******++ีี%>737.'5'.4670I@hP€I0Ph๊2NN2QooQlH0Pi๊0HAhPVlVAzฆz++ีี %6737&'5'.467 ˜iO( Oi๊QooQร Ml๊ ˜lM("("˜zฆz @Uภซ #'+!!53!5353#53#53#53#53753#53#53@€€ีซ€ซช+€+€*€+€+๊k๕j๕kซVU++++ซ++++++++++V******@Uภซ !!5!5!%5!@€€€€€€€ซVj@@–@++k•/$264&"'#53.#"326%"&547##"&46;'#53{4&&4&ฎ<<!&&!)8>Z=;#9'->>-๗+L^•&4&&4+&4&’<+-==-;%0=Z>+*U2ซฮ7!4/7"&54?€&ZZ&๙2dŽd2yี7&^]&H2FGddGF2y+*ีภ+3;CK%&+"'&'&>7>7676363226462"&462"&462"462"r     'SS'  !   ",,,,Ÿ,,,,ร * ** (  (Q, ,u,, ,, 6, ,ซ+Uี %##5#5462&462"U@@*&4&*•$$๋Vjj–&&$$@๊ี'64''64'2!546462"ฌ>>#,,# $ธluชV2F22FีAซ>"3ƒ0%#X $-ช/&++&ŒF22F2 @@ภภ #'+/3#54&+5325335335353#535353'53533#ภ+&jj,?€+*++*€+€+++++++*++++Ujj&+?ฟ++++++U++++U++ซ++V**V++*+Uภ๋"'%'5'#56?6;2'532"&4#'ภ@@— 8L† Q""`6+K @@@ —.!{L1 †k""๚6J €๋€3#"'"&547'"'"&46327&5462627&5462๋L"7 a"a"6L"f"L7a"a6K@@ภภ'5#7.27653"'&4627 J[ี‘;,{,+V|,++88 88pž8:UZ-7j-<,+,xX,+=O7887p8<++ีี %7'5#&2"&4Z` Cฐ}}ฐ}ฆ:o€๊}ฐ}}ฐ๋,+"/636325462354623546235462๋3#›$จ \‹ส#2ซ4  –หหถ  ถv @@ภภ'%27#"&'#53&47#53>32&#"3#3#@3'&6J>cKAAKc>J6&'3'B{‰‰{Au"&1H8++8H1&")"++")++ีี!%AU%4&+3533#'326'>?#7&'4'#3#"&46327'&#"32672+'#"&546;ภ ผ)M ::+– –  U 8T/ !$33$%/พซ–€U V'$99;+ ฑ/ F ! * 3H30œ๊@@ๅๅ-62"&47'!23'#'#"&547'#"&54?/7„""แภ L วl+2L= Ÿ/^€""…ภ Š++#ฅ=  5c^U+ซี%%264&#"'3'632#"'##!"&5463,??,: U".,,'% 5L€€?X>1U"(+>, #U€Vี 2'35#'35#'3'5#'#'ซ•i”ฉซ~)++++eบzภU+ี•++)~ภ+k++FzUj+k@•ภ3!537377'7''!+"&KJึJj.-------Sชซ++ฝ.----...ถ++ีี+#'#'#5'75#'7#53'735'753737ีYEc+dE*Ed+cEYYEc+dE*Ed+cE*Ed+cEYYEc+dE*Ed+cEYYEc+dEk๋• /3'#264&"'5#264&"'35#%#"&5#"&5#5463@kV(@UN UU+€6&4&u&4&+VเwVVŠwV*€j&&&&ภs'2"/7264&""&462'&"276?]`CC`" <**<G5!D^D"*<*>5?@ภว.6.7&474367t2ƒ>0s1z11 2=??FbQ8™‰D2 11z1s0>ƒ2=?ณ?F8ธŠ+@ีภ+5#32+5##"&=46;573353#!"&=3+Vึ€V€U+U+€V•ึ•k**@++@*++*ภUU@@ภภ'7264&"264&"&264&"&264&"264&"2#!"&5463RNNึ@าNNา.ึ*@๋ภ%CKS73"&26723264&#"#.""&#"3263%"&'.'&47>767632$462"6462" ภ 4@4!fO  OfO  k _r^!!6I:]!ล[ี##M0% ++ภี(-$264&"264&"%#"&'##"&547'#53!'2#^ญ+*-),'-/J7ซGdซUE!+%%+,^++9๙dF++ีี%'''7''7'7'7777ท--LทL--LทLร--LทL--LทLU@ีภ7!!5#72++"&=UVชV+++2#€#3k+@@k@@#33#ีU+ภี"&5473462"k€.7&5462!!'?V€V?"ชVZ aAAa ฯ*+ีี)-19%'3'"&463";2#54#54춭3#73#%7'#53k??6**!, <% 1$ ' 5 vk•ี•ฌ?[+:* '"R:$,,$3 ย@@@ี•–@+kีี,0482#54&+"&463"37#54춮43#73#%!!V+ !**]% 1$*' 5 v@ภ&&#+:* 5:#00$2  *ล@@@@@++ีี&'672>3&'.J* *CC็Dp!!pD_LL_3""_CCgB33BT††+ีN#'"/+"/+"57#+"/+"5'75&5!7&76'&'"'&743 / ‡#" 0/  $ )> pˆQb‡BDf'<8R " %ฎ0bฑะ),tก ` “ ภ Rุ V ƒ *ฐCopyright 2015 Google, Inc. All Rights Reserved.Copyright 2015 Google, Inc. All Rights Reserved.Material IconsMaterial IconsRegularRegularFontForge 2.0 : Material Icons : 8-2-2016FontForge 2.0 : Material Icons : 8-2-2016Material IconsMaterial IconsVersion 1.011 Version 1.011 MaterialIcons-RegularMaterialIcons-Regularƒ2อ    DEFGHIJKLMNOPQRSTUVWXYZ[\]  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~‹€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ กขฃคฅฆงจฉชซฌญฎฏฐ0123456789_error error_outlinewarning add_alertalbumav_timerclosed_caption equalizerexplicit fast_forward fast_rewindgameshearing high_qualityloopmicmic_nonemic_offmovie library_add library_books library_music new_releasesnot_interestedpausepause_circle_filledpause_circle_outline play_arrowplay_circle_filledplay_circle_outline playlist_addqueue queue_musicradio recent_actorsrepeat repeat_onereplayshuffle skip_next skip_previoussnoozestop subtitlessurround_sound video_libraryvideocam videocam_off volume_down volume_mute volume_off volume_upwebhd sort_by_alphaairplay forward_10 forward_30 forward_5 replay_10 replay_30replay_5 add_to_queue fiber_dvr fiber_new playlist_play art_trackfiber_manual_recordfiber_smart_record music_video subscriptionsplaylist_add_checkqueue_play_nextremove_from_queueslow_motion_video web_asset fiber_pinbranding_watermarkcall_to_actionfeatured_play_listfeatured_videonote video_call video_labelbusinesscallcall_end call_made call_merge call_missed call_received call_splitchat clear_allcommentcontacts dialer_sipdialpademailforum import_exportinvert_colors_off live_help location_off location_onmessage chat_bubblechat_bubble_outlineno_simphoneportable_wifi_off contact_phone contact_mail ring_volume speaker_phonestay_current_landscapestay_current_portraitstay_primary_landscapestay_primary_portrait swap_callstextsms voicemailvpn_keyphonelink_erasephonelink_lockphonelink_ringphonelink_setuppresent_to_allimport_contacts mail_outline screen_sharestop_screen_sharecall_missed_outgoingrss_feedaddadd_box add_circleadd_circle_outlinearchive backspaceblockclear content_copy content_cut content_pastecreatedrafts filter_listflagforwardgestureinboxlinkmail markunreadredoremove remove_circleremove_circle_outlinereply reply_allreportsave select_allsendsort text_formatundo font_download move_to_inbox unarchive next_weekweekend delete_sweep low_priority access_alarm access_alarms access_time add_alarmairplanemode_inactiveairplanemode_active battery_alertbattery_charging_full battery_full battery_stdbattery_unknown bluetoothbluetooth_connectedbluetooth_disabledbluetooth_searchingbrightness_autobrightness_highbrightness_lowbrightness_medium data_usagedeveloper_modedevicesdvr gps_fixed gps_not_fixedgps_offlocation_disabledlocation_searching graphic_eq network_cell network_wifinfc wallpaperwidgetsscreen_lock_landscapescreen_lock_portraitscreen_lock_rotationscreen_rotation sd_storagesettings_system_daydreamsignal_cellular_4_bar+signal_cellular_connected_no_internet_4_barsignal_cellular_no_simsignal_cellular_nullsignal_cellular_offsignal_wifi_4_barsignal_wifi_4_bar_locksignal_wifi_offstorageusb wifi_lockwifi_tethering attach_file attach_money border_all border_bottom border_clear border_colorborder_horizontal border_inner border_left border_outer border_right border_style border_topborder_verticalformat_align_centerformat_align_justifyformat_align_leftformat_align_right format_bold format_clearformat_color_fillformat_color_resetformat_color_textformat_indent_decreaseformat_indent_increase format_italicformat_line_spacingformat_list_bulletedformat_list_numbered format_paint format_quote format_sizeformat_strikethroughformat_textdirection_l_to_rformat_textdirection_r_to_lformat_underlined functions insert_chartinsert_commentinsert_drive_fileinsert_emoticoninsert_invitation insert_link insert_photo merge_type mode_comment mode_editpublish space_barstrikethrough_svertical_align_bottomvertical_align_centervertical_align_top wrap_text money_off drag_handle format_shapes highlight linear_scale short_text text_fieldsmonetization_ontitle attachmentcloud cloud_circle cloud_donecloud_download cloud_off cloud_queue cloud_upload file_download file_uploadfolder folder_open folder_sharedcreate_new_foldercastcast_connectedcomputer desktop_macdesktop_windowsdeveloper_boarddockgamepadheadset headset_mickeyboardkeyboard_arrow_downkeyboard_arrow_leftkeyboard_arrow_rightkeyboard_arrow_upkeyboard_backspacekeyboard_capslock keyboard_hidekeyboard_return keyboard_tabkeyboard_voicelaptoplaptop_chromebook laptop_maclaptop_windowsmemorymouse phone_android phone_iphone phonelink phonelink_offrouterscannersecuritysim_card smartphonespeaker speaker_grouptablettablet_android tablet_mactoystvwatch device_hub power_input devices_othervideogame_asset add_to_photosadjust assistantassistant_photo audiotrack blur_circular blur_linearblur_offblur_on brightness_1 brightness_2 brightness_3 brightness_4 brightness_5 brightness_6 brightness_7 broken_imagebrushcamera camera_alt camera_front camera_rear camera_rollcenter_focus_strongcenter_focus_weak collections color_lenscolorizecompare control_pointcontrol_point_duplicate crop_16_9crop_3_2cropcrop_5_4crop_7_5crop_din crop_freecrop_landscape crop_original crop_portrait crop_squaredehazedetailseditexposureexposure_neg_1exposure_neg_2exposure_plus_1exposure_plus_2 exposure_zerofilter_1filter_2filter_3filterfilter_4filter_5filter_6filter_7filter_8filter_9 filter_9_plusfilter_b_and_wfilter_center_focus filter_drama filter_frames filter_hdr filter_nonefilter_tilt_shiftfilter_vintageflare flash_auto flash_offflash_onflipgradientgraingrid_offgrid_onhdr_offhdr_on hdr_stronghdr_weakhealingimageimage_aspect_ratioiso landscapeleak_add leak_removelenslooks_3lookslooks_4looks_5looks_6 looks_one looks_twoloupemonochrome_photosmovie_creation music_notenature nature_peoplenavigate_before navigate_nextpalettepanoramapanorama_fish_eyepanorama_horizontalpanorama_verticalpanorama_wide_anglephoto photo_album photo_camera photo_librarypicture_as_pdfportraitremove_red_eyerotate_90_degrees_ccw rotate_left rotate_right slideshow straightenstyle switch_camera switch_video tag_facestexture timelapsetimer_10timer_3timer timer_offtonality transformtune view_comfy view_compactwb_auto wb_cloudywb_incandescentwb_sunnycollections_bookmarkphoto_size_select_actualphoto_size_select_largephoto_size_select_smallvignette wb_iridescent crop_rotate linked_camera add_a_photo movie_filter photo_filter burst_modebeenhere directionsdirections_bikedirections_busdirections_cardirections_boatdirections_subwaydirections_railwaydirections_transitdirections_walkflighthotellayers layers_clear local_airport local_atmlocal_activity local_bar local_cafelocal_car_washlocal_convenience_store local_drink local_floristlocal_gas_stationlocal_grocery_storelocal_hospital local_hotellocal_laundry_service local_library local_mall local_movies local_offer local_parkinglocal_pharmacy local_phone local_pizza local_playlocal_post_officelocal_printshop local_dining local_seelocal_shipping local_taxi person_pinmap my_location navigationpin_dropplace rate_reviewrestaurant_menu satellitestore_mall_directoryterraintrafficdirections_run add_location edit_locationnear_meperson_pin_circle zoom_out_map restaurant ev_station streetviewsubwaytraintramtransfer_within_a_stationapps arrow_backarrow_drop_downarrow_drop_down_circle arrow_drop_up arrow_forwardcancelcheck chevron_left chevron_rightclose expand_less expand_more fullscreenfullscreen_exitmenu more_horiz more_vertrefresh unfold_less unfold_more arrow_upwardsubdirectory_arrow_leftsubdirectory_arrow_rightarrow_downward first_page last_pageadbbluetooth_audio disc_fulldo_not_disturb_altdo_not_disturb drive_etaevent_available event_busy event_notefolder_specialmmsmorenetwork_lockedphone_bluetooth_speakerphone_forwarded phone_in_talk phone_locked phone_missed phone_pausedsd_cardsim_card_alertsms sms_failedsync sync_disabled sync_problem system_update tap_and_play time_to_leave vibration voice_chatvpn_lockairline_seat_flatairline_seat_flat_angledairline_seat_individual_suiteairline_seat_legroom_extraairline_seat_legroom_normalairline_seat_legroom_reducedairline_seat_recline_extraairline_seat_recline_normalconfirmation_numberlive_tvondemand_videopersonal_videopowerwcwifienhanced_encryption network_check no_encryption rv_hookupdo_not_disturb_offdo_not_disturb_on priority_high pie_chartpie_chart_outlined bubble_chartmultiline_chart show_chartcakedomaingroup group_add location_citymoodmood_bad notificationsnotifications_nonenotifications_offnotifications_activenotifications_pausedpages party_modepeoplepeople_outlineperson person_addperson_outlineplus_onepollpublicschoolsharewhatshotsentiment_dissatisfiedsentiment_neutralsentiment_satisfiedsentiment_very_dissatisfiedsentiment_very_satisfied check_boxcheck_box_outline_blankradio_button_uncheckedradio_button_checkedstar star_half star_border 3d_rotation accessibilityaccount_balanceaccount_balance_wallet account_boxaccount_circleadd_shopping_cartalarm alarm_add alarm_offalarm_onandroid announcement aspect_ratio assessment assignmentassignment_indassignment_lateassignment_returnassignment_returnedassignment_turned_in autorenewbackupbookbookmarkbookmark_border bug_reportbuildcachedchange_history check_circlechrome_reader_modeclasscode credit_card dashboarddelete descriptiondnsdonedone_allevent exit_to_appexplore extensionfacefavoritefavorite_borderfeedback find_in_page find_replace flip_to_back flip_to_frontget_appgrade group_workhelp highlight_offhistoryhomehourglass_emptyhourglass_fullhttpsinfo info_outlineinput invert_colorslabel label_outlinelanguagelaunchlistlock lock_open lock_outlineloyaltymarkunread_mailboxnote_addopen_in_browser open_in_new open_withpageviewpaymentperm_camera_micperm_contact_calendarperm_data_settingperm_device_information perm_identity perm_mediaperm_phone_msgperm_scan_wifipicture_in_picturepolymerpower_settings_newprint query_builderquestion_answerreceiptredeemreport_problemrestoreroomschedulesearchsettingssettings_applicationssettings_backup_restoresettings_bluetooth settings_cellsettings_brightnesssettings_ethernetsettings_input_antennasettings_input_componentsettings_input_compositesettings_input_hdmisettings_input_svideosettings_overscansettings_phonesettings_powersettings_remotesettings_voiceshopshop_twoshopping_basket shopping_cart speaker_notes spellcheckstarsstoresubjectsupervisor_account swap_horiz swap_vertswap_vertical_circlesystem_update_alttabtab_unselectedtheaters thumb_downthumb_upthumbs_up_downtoctodaytoll track_changes translate trending_down trending_flat trending_up turned_in turned_in_not verified_user view_agenda view_array view_carousel view_columnview_day view_headline view_list view_module view_quilt view_stream view_week visibilityvisibility_off card_giftcardcard_membership card_travelworkyoutube_searched_forejectcamera_enhance help_outlinereorderzoom_inzoom_outhttp event_seat flight_landflight_takeoff play_for_workgifindeterminate_check_box offline_pinall_out fingerprintgavellightbulb_outlinepicture_in_picture_altimportant_devices touch_app accessiblecompare_arrows date_range donut_large donut_small line_style line_weight motorcycleopacitypetspregnant_womanrecord_voice_overrounded_cornerrowingtimelineupdate watch_laterpan_tool euro_symbol g_translateremove_shopping_cart restore_pagespeaker_notes_offdelete_foreverac_unitairport_shuttle all_inclusive beach_accessbusiness_centercasino child_carechild_friendlyfitness_centerfree_breakfast golf_coursehot_tubkitchenpool room_service smoke_free smoking_roomsspagoat '(ฬ ,latnligah8Tโ์ จ)เ+n-L/ฒ1n9Ž=0@ @ชJบKžP^Zb2bคf’hŠhธโ  !!H’ฮ@xฎไDržศ๐>b†ชฬ์ ,LlŒชศๆ:VrŽจย๖*D\tŒคบะๆ(>RfzŽขถส๎.>N\ht~†ค ! #" "!ง !  "ฆ !  ฉ !  ฅ !  %!จ !  %!ฃ ! ! j$  $ ๅ"!  $! !#๖ ! !" ๕ ! !" !#ฑ  "!่  !๔ ! !"ข ! ! ๓ ! !‹  !! !i$  $ ป! "!!ไ"! ็"! $ $$๒ ! ผ  " #k $  "ˆ  ! ! l $ $ใ  !&ุ   | $ "$ !! &f  ! ""๏ ! !๎ "![  !ื  ๆ "! %ู  !   ! !! ๑ !h $ ข  ฐ  ๐  !Œ "!J !!!j ! !๋  +  !ฺ  ๊  ๗ "!$Š  !!์ -# !š "!ฒ#ฏ %ํ_&บ "!‰" !้,"g ฎ‚7pœฤ์8\€ขยโ"Bb‚ ผุ๔(B\vชฤ๘,F`z”ฎฦ๖ "8Nbvˆšฌพฮ่๔!!&  "ใ"!! !ๅ"!! u $!ไ"!!  ้! " '!๛ "!ๆ! "!พ"  ! #!็! ƒ"!! "แ!!& "$๛ ่! $  !! " " !!& !  — !     – ! • ! ” ! “ ! ’ ! ‘ ! บ " !˜  ฝ     !&฿ !!& "  !  "!Ž "   !เ !!& !  !   " !" " ! โ "!!ณ  #๚" |"  " ๘"™" "ด๙Vฎ8bŠฒฺ$HjŠจฦไ >\z˜ดะ์$@\x’ฌฦเ๚.F^vŽฆพึ๎6LbxŽคบฮโ๖ 2FXj|Ž ฒฤึๆ๖ .<HT`lx‚Œ– ชฆ! ! "! % "! ! ฌ   "!“! " "!Ÿ! " !ช! "  W! $  ! " $ช! "ˆ  N" $  !&.  !ฃ $ Y ! !ม & v ! !ฎ  ฅ ! !p # !ธ !!  !‡  !— !! ฏ  ‚  #ฐ  !!˜ !! Q " "œ  !ถ !! &L " o # !  ‰  !#  !!ก ! ž    ’ ! "ท !! "!P " ""ฑ  " !    ภ  ƒ  !ข   M " ›  !€  …  ญ  ง     %› &!O "   ‡!! ฃ'ซ ช  จ  ฌ Z"!~ ค†!mฟ น!šK"ต nq ฉ„!}ฝX !/`†ฌา๘@b„ฆฦๆ&Fd‚ พ๚0H`xจพิ๊*>Rdt„”ขฐพฬึเ่ต !  !" … !  !" !+! ! !*! $&)! "$&ถ !  !" … $  *! !(! &(! !,! $%! ]# \ ! $$ Z! "'! ๋# &! " † !  !"น! #† # !ี ! $ฆ "! [  !   !ฅ "! B  $ ! „ # "ˆ  ค ! ๊ ! " ‡ # !„   "    ณ! ์# ‰ฒ'พบ! ! ^ ํ#>n–ธฺ๚:Xv’ฎสโ๚*@Vl‚–ชผฮ๊๖ ! ! !ฑ &!ท% " " ถ% " " ˆ#! #น% " " ธ% " " ท% "  ถ% "  บ % " '\ ! !)  "!r %   %! ! s % ด " &‰ #! " &“ #! !a # !!Š #! !/ "' %! ต% "0%! %ŠŒ! #!(ด!Zถ๎&T‚ฌึ*Rzขส๐<b†ชฮ๒8Zzšธึ๔0Nlˆคภ๘0Lf€šดฮ่4Ld|”ฌฤ๔  6LbxŒ ดศ๐   & 8 J \ n € ’ ค ถ ศ ุ ๆ ๔    $ .-! !%!!  ! ,! !%!!  ! #! !  "! !  &!  ! "!+! !!"'!  ! "!  " !&%!  !  !ว! ! " k "  !   !l ! !  !w!" &  !ฬ! !! !!!  !%!!  !.! "!  .! "#! "!#! u"  %!ฦ!   $•! !ย! !x!" #อ! #!ร  !‹ ล ! " R  $ะ ! $$ ! !V  ษ !    ! !C !  ) ! "!  !  ! ศ ! ( ! !     1  ! $ห ! ป !  !œ !* ! 'U  ” !  ! 2  ! $S  "t " a $ ส ! ฯ   "!` $ €  ! ะ   / "! t  h  $b $ g  #ย! ม! ภ! ฟ! ฝ! ผ! ป! ั  #!ฤ! ร! ฝ$-!Tพ!‹"ฮาผ,H`xŽคธฬ๐ 0@LXdp|†๏  ! %ฤ  " ต ! !๓   " $ภ " ๎  %ี ำ!ึ ! พ !"_๐ #3 ิฟ"ฬ!—0Rrฌฦเ๘"4DTdt„”ขฎบฤฮุ! #" !&" " ! 5  "!&Ž  "!a  ! ู  !D !ฺ $ !&` !ล! !"4ื ุ .! !! ’!!],\‚ฆส๎4Tt’ฎสไ0<HT^˜!!  %  ! !2 ! # #!   !! # 4 ! #!!$#!  จ! !! 3 ! !1 ! !$ #!  Œ ! %!"  "!6  ! !0  ! !5  ! #"!ฟ%!  Fn–ผเ&Fd€šฌe& $ !d& $ !c& $ $g&  f& $ "h&  w& !j& !"l& #i & k & !b&ฦ!O ะ$Jn’ถฺ Bd†ฆฤโ<Zv’ฎสๆ:VpŠคพุ๒ $<Tl„œดฬไ๚&<Rh|คธฬเ๔.@P`p€ ฎผสึโ๎๘ 7 # !> "& #; & !๒! H  ! :  !!๑!  ž!" "!n! N!  !&3 !!!I ! J  !"!I ! 6  $ 3 !#!&L <  !p! $$ D &9   !C    ?  &= & " < &  ม ! !&1  !&  "! ! ,  "!J  0 & ึ $ !&A  # E   ! F  ''8  ; & แ  #=  !B  E  จ  $!ง  !&@  5  o ! M  !%G  &K  4  2  !  ! ้  !$+  ่  ฿  Ž # เ '"-&!&็ ๆ ๅ ซ# !#ใ /& ("m!๊"ไ %โ ภ6) !*'Pvšพเ$Dd‚ ผึ๐ ":Pf|’จผะไ๘ ,<JVblv€Š’š." %๋ ! “  "!<& &  ๓"!! "=& & " H!'! ป"! !์# !;& & ั # ! %ฉ  "!8  ! # !P & !m "  #w  '7  !&ํ "  !ย "ฉ !&x  #!9  !A & ’  ร 8 9 ‘ q&r" :#ยมv"OŒ 76`Šฐิ๖4RnŠฆย๚.H`vŠœฌบศาศ!! " ว!! !#ล!! ฦ!! ว!! ๐#! Ž!$ ?! ! !ณ  &!ฤ !! ฒ !$ ๑ #! %!๗ $ $๏ !" > $   ๕ !$ $๔ !$ ๘ $ $! Q #!ำ %! $/! ] ” ๎!"y!๖.Ld|0  $ ฌ #1   $™  2  $!ช!&WฐโBrขะ&Nvžฦ์8^„จฬ๐8Z|žภเ @^|šธึ๔0Nlˆคภ๘0Lf€šดฮ่2H^tŠ ดศฺ์  " 4 D T d t ‚  ž ช ถ ย ฮ ฺ ๆ ๒ ! ' ! !"! ' ! ! ' !  "!! 8 # !Ÿ!"  !" !6 !! B"   "!E&  "!A"   ๗ $ ๕ '!?$ !! $D&  น ! "!o& !  =!"  !"7 ! !!^   ๖ #!–! $ ๔   &D&  8 # 6 !! ๔  &5  ฃ   $ฆ !"ญ  #ฅ ฬ "!ฯ  "!<  $;   ค ง ! ! !"  ฌ! $ท !& 9  !!&– &  $s  v  i & ! &๛ ! &‘   !”  " “   ’  t  ๚ ! F & ! ! ! !๙ ! "… $ "!C & $:  ฮ   N   ส !& ธ  !u R !!ะ" ๓ณ !3#$4&!:" ๒!!>&า"หอ @!@" ๘!ฎ$S•ษ ัวซ! <d„คภุ—" !! ˜" !! p"" & %!B" !  $A "& "H "" " G"",Zˆดเ 4X|žพ8TpŠคพึ๎2FZn‚”ฆถฦึๆ๔,:HT`j "!! "ฦ#  "!!!   $ถ#  ! "!! q#  ""ญ # # "!! U !"! " "!! ฎ" #  &E! ล # J ! ! ศ  #ท  !  !! !™  #"T ! #$ !! !`  !"!L ! ศ & d & c & ด # "e& ญ F !C!y ฏ$K!ษ!ฤ#w"!DM&ว&IGรu์D|ฎเDvฆึ2`Žผ๊Bnšฦ๒Fpšฤ์<dŠฐิ๘@dˆฌะ๒6Vv–ถึ๔  0 N l ˆ ค ภ ๘  0 L h „   ผ ึ ๐ $ < R h ~ ” ช ภ ึ ์   . D X l € ” จ ผ ะ โ ๔   * : J Z j z Š ˜ ฆ ด ย ฮ ฺ ๆ ๒ $.8@+ " !  !!  ู!! #&  ! !! & ! &S!! "!  !R!! "! !ฺ!! #& ! ~"!& $ !}"!& $ !L!! "  ! "  Q!! "! !!& &  ›!& "!   $   ึ!!  ! U!! "! #K!! ! œ!& "! !! "  ž!& & !!๙    " "W!  !&๛  !!๚  !!g$ #!  " O!! ! T!! "! ุ!! ! M!! "!!d"#  "! $  ซ!  P!! !!ื!! "!V!! # ธ ! r$ ! #h& ! "! !O!!  &g$ #! h& ! "! !#<!!"  !!]  ! $ Y!! !–  !T"" "X!! $W!! Z!! # $! _  ! }  "^ ! & ส   œ & ! "!^  !P  #" n " ! N !! š &  š   $! #› & แ ! "!ช  แ ! ` ˜  e $ 'ษ  { !ห !   !Ÿ $  b !!#$ !!ผ $ !F ! !%!S "!! เ !   $f $ #!O  %!;  V !!y"!&z \ !$H"J!! c"!N"• |x!ำQ'Ic"$&a! ิ!&b!อ!฿![ฬ™&ส#R!— ห-\ฒะ๎ (D`|˜ฒสโ๚&:NbvŠžฒฤึ่๚ ,<L\jv‚Žšคฎธยสาf  $!  !!u "!n" " $! j " !r   ž  ! #x "  !t  $u  !   &v  "ฮ %! !G %!  l " $€ ! ก "          w " s  !m" "!&k!   ฐ%!" %!  Y  X~!I! dp&& e"qoi‚#&>R`j{  z   า #ฑ!ฯ %Lxคส์.Lh„ ผึ๐ 8Ph€˜ฎฤฺ๐2FZn‚–จบฬ>!  !=!  !!?!  !U !$ !‡  !† !&  $ U  &| $ " y  "  $ !W  ƒ $ !} $ "{   $ "z $ X " $Y " "!…  !&{ $ &z     ! $ &‚ $ "!Z " Ÿ !„ $ $€ $  !ก [ " "!!~$ &Vก ข &0Tt”ฒฮ๊*>RfxŠšชบสึเ๊๒ˆ!    !‡! !  !!   !‰ ! !#ฒ ! !s   !๗ @  !%!    "&ี! ! "&๘!  "!ิ*ƒ!Šฐ\ฏ‹"!"  "4_  "! ‘ "! $ &' 4latnsize าr‹xาฃษาฃสZoneMinder-1.32.2/web/fonts/glyphicons-halflings-regular.woff0000644000000000000000000005560013365153155022740 0ustar rootrootwOFF[€ฑ\FFTMXm*—GDEFt DOS/2”E`gนk‰cmapภrฺญใcvt œ(๘gasp glyfจM”ค}]ยoheadQภ46M/ุhheaQ๔$ DhmtxROtาว `locaS`'0o๛•ฮmaxpUˆ jุnameUจžขณ, ›postWH- ับฃๅ5webf[xรTPฬ=ขฯะvuะvs—xฺc`d``เb `b`d`d’,`HJxฺc`f้fœภภสภยฬรt! B3.a0bฺไฅ€ ‰๊๎วเภ ๐?๓@u" ี@aF$% Œ1– xฺํ”?hSAว—คiSฤฦ‰m฿ฝ44ฑญะ,qสPKƒ qา าXE]ฒ(2 ‡.ฅิฉƒ]ด‚ "EœDท ญฅน฿i]DิกZJ๕๙ฝ\ตบ8๙เ“ฯ๏ฝw๏๎›wฟ„ˆšˆศV"ฑFฆpUิฏ๛ืโ.ฮง(ƒg’Kร4O nซ;โNธ‹๎R{ผg`'!ษPฒM๙UHEี Jซฌสซ‚*ชฒชชYq”9อcœๅ<นฬUžแ9ิ!ัQำIึYื…-ฯ๓ฐขKC๕•่+ ีคยสU)•Q9ฌ4ฉJชขฆYp˜]Nq†ว9ว.q…งyVV ๋nฌื)ั9ป’๗ส[๕ฮ{“ชฅ๖บvฟVฌๅื›ึพฌ๖ญFWb++{>ทืธa|ใ€*ทไg๙Qพ•๒Šผ,K๒‚<'ฯสำ๒คW@๕ExฬขพDฤศร&รUุdธ#› หศภ&ร x ๕Mx˜<ทaไa“็Œ,l2<€‘M†ว02ฐษ๐6๓ฮ ^†‘…็Pฟ$า6{ธ‡,ด#›ฦžะ{ MฮwpฬB๏แ8Hข#ณ6™7adึ&'~‰95r 3wม"ะ[žEt’ุW‡:ำญ:$"๔™>2อc ๒5*฿.lŸ๔ไN /๖๘h‹ฑม]Gt้T่฿฿ั(๘xฺลฝ |ีต0>wึm#Yถe[’%Y-YR'r๖„ฤYศjถฐD% ,@ุBุKZjHฺูค@bฅฅค-…R๔ส๋+ฅn๛hK›~ฏๅห๋ฃผ–ฆฅ$ึไ;็ฮhต่๋^f๎นs๏น็n็œ{ฮน หด0 นŒkb8Fd:Ÿ%Lื”ร"ฯ1ฌA๘ี”ร AๆYฃŒ>,ศุ”รใŽ€#œp„Zศ4๕ุŸฤ5ma฿d“eฒ ?ศœyว=ค๘I:C‰ว “Dใ๕(nIคxˆL ‘.1ข!„P'™JD‰t‘Hj€@L4’์…P†๔“h' )ศbา)vHŽX,f๙1ัc\'ฒโcGอึภฑ„u˜>๋Œ๑1ู ~ยtเ?๑๑„้๘!xก๘มT_qโ?qB‡ะF‚‘ค#ŒL%ฝฉDภั›"ฏไ?Yญ๘Šบวฏภƒบศj??8>NัSkem„ฒฆAYณตฒDbก4 ูJ)ค•;•@นj“ลP$ ˜๏'qhฎ8`›๋;aŒ๊Xอร6C๙Fโ*„dหYcแฑฺ"ิŒ‚๙„ฯ๙ฃ๘'?hฦLฌVใ—Œึ,ฝ>c‘eห3eVนาh† =C๛‘ภš†้ว~ไ๕xCฝ\((qb@ ธ4๐‰x†K&hืฮ ฅฉ4\2บวฑ6N1|-ิ;ญฯ๋j›๏–ษ Yuิ@†แj๊๎›ัซx๚ฆ๕†žiธไŠงmK๓ ู๋D๘E๖wฆq3ฬท.ปผcAw@ถ4t.Žgใ๑์kgดษrฐ{~ำWl~ฌ{ึlW2ป๊๖๐} ร27ะ6a2ฬ\€6o”z@ณ$ด๑ก๋HฤSษHˆ ซgฎ›ัํฑb๕tฯX7๓0Kštิc1ม,ซี7—B๒ oL๋่˜ฦิ6ฤท6[,–ๆึ%๑iแZ ฟ‡,’l>T†p๒KณœSGgฌ\> ๑#ป๘A๖#3ŒซุEตŽy‚kย๎6vฎ์ฺ็่…มบ็†;u3ำ!ZIฮ8์˜M†k?ณ8ถC˜ฃWq{`์C*‰ะh>H1ฟ_s๖ูkโŠ์h)œ›žฎojชOO'ป !~dX๑gฯ๛B(๔…๓†ส0< kOYxสeงฌฉฑฦงฤญ5kฆ —=d ใฒ๐ำฯง> ํ+tฦC็-o วช†„/ไร๕_ko๏ถถผs๑ี+fฐฮO฿zฑtpu7-™}…d๕ํ9ล sเˆฉe Œณ\9.H4’!0ฅS\ สฑk2™๏"?ip7œ\2zงูิิlฐัt=น๎…W๙็\!๛KyOXimUย์พnov›์ ว6ฒ:ฝ่ๅ 2ฦ ๓LZkAAอ^โqCๆ™” &Pๆˆ๏aFฦ๊ฅIช0ใ>&๏๙…Q #FฃQปัlๆ> Aณฮ‡q*˜O๎€„‚แญใฯ‹ัศฆๆ_@27ฆฬlฤ,จ‚s๘‰ั่พfศ พ6โp7ฉ?๚M›Œ‰ฑ1vˆA˜2‰ฆ]$j"‚‹;‘vlk~va0ฟ๛g์ฅjฃ๚z›ถRD:gฉืๆฟฑc๋6’ลลyw๚%๔gโ(รพƒุ#'ดฮuBต๒#์=๙_@?ƒ>ฦFุVbŒ0žaแ!ฌaL4tXvผก:ลF๖๖h๗ฒ9‹๏j^ตxŒพก้zะฯ}ดWnŽ}7}ถืปj“ฏฦฮšฮ๏iฝHฉญฟบถฦมiฅลt๊๏KSŒŸ‰aอXE๔บEบbbBQ1ุษ๖f”t‘x†๔Fศฎ ท-"dqA๗ืฦ\๊ท~F`ณป่6ฒiไ•+ภ ิข^ศฒย}ืฉ๏ื†k&ปฤนพปํศเ<- \่œ;ฤโg1>จw†00฿v๒ษ^x ์7l<”šyฮ}™ฬSทo›9้-ฎใพ6kžะฑlหดพ๋nน๙๐Š‡oพๅบพi[–u๓—ง~ยฌๆoเ`jเม•Š{iื\C4,"iW8’Jo๑V๒„bpจ๛wˆฒCชบ฿!‹;ิ'7ืD.vนิวึิ n‹๕oZ-nฒ๋คีมฐe๙ฯP‚๒Šio4Š~LYไ/zmw_๒พุ์gฯฝŸงRฯ"t ำ&NoN€ฅ)4ฦษM ณCG2ซ‰\jฒส8d-ษ@>#Ot^ถภ์5ผ+xอ˜e.^แ]ƒืผเฒฮG 8›^ๆ ๔mŒ๗(ำฦt1 ๗s™ฬbfณJ›ฐ฿า —%‘Œ‘ โ<‰ฌ4ŽHโ๘”ล@eš๊๗8Cศิ๒Š,ป5<ย(—ๅkฒc5YฎIฟถ์อุ—โ๙A๘๚]|ลืšๅl6+›ี=๘HVใcbดKี‹Bด6฿i4• #ด‹_ฉ|&๓>NvQŠk#ฎpW•=ไฟuบ7”HษฐR$ ็๗๎ ณ[5์‹™ ™ ฬอฬgก ญต้%๐1ฯไ9}ผฐะ๛ษว๚ฯ&@$&ธจน๗Œกlฌ”๐=ำ1RI๑๒}9ๆุ#ฮฯ‚ซz๛??1z&ฎ๔ฤฑ_aฺ๙c|PŒI[ํ:uะ; ฏไวlฟั->k4ŽๆG๕ฃ๑Yัm|Z๔wเ }๛“ัHnัR=-Bพ๋™๕ ~ๅmผง‰.ูฑ .ภร๕ฆMz^,—ป๋0%ฃ๑ฐส8ฎ‰EซGฌฤ**|sg|oิ๒ฑŠภนzOฟึฌ0s–พzโฺ้.ฌใWNถ^ฑ ‹„yHk<v3t{8-ู|ร' ๘ซe๊๘a~ึ์ลH94ฒศxผขืAล-ณ@y bT4@0์b#]DŒDำั“ฝlj€DSio:Agะเ๖ฤSฝ์P z:„;ฆถ-แ|yH"r ทค{าB{\ˆด5RLi‡6๖AƒแAึๆใ–tMพ]่่฿tำแ›๘a๘R๒ฑKบ๛เหCคญ!ุ1๔ดCํgC์ƒ‚๐ +๙ธณ1EGท!าฺ€Xz๛––ฎ๎พู้›nz–ตv@ฑx™๔ํ๔›ส-#i^ ฯx๑*$)ฎฮภW’ใค=ึO\f๙ไ๓€[WŒด“๖X~Vฌ?ซ๎ะ `Leiฌ::v4ึ฿$?‹=R๓˜•a#cค๗]8YๅFJโ™b&'{%LC์Eภฯขญ‰อ ทCf]ว^$Š๙/อ๙๏าf฿ชM;ร€ฺ;ซ๓ษœ… ฅฐส„6๙ฐCXีVธฅภง๐#๊ฦX~ F๐ƒฺ<็ :vCฟฏcบตyBpLvŠฃฌำ1๐ะF”v#฿9† /๊๖8VF๋ฉ01ำเญ_K๔ํ?สๆx>ฃ}๚ิ#€G7ิั‚\WŒp!.@ธ๎ปฑb๙wษก+{oชีญ#ปิPรQาฎnฤซ66 cZญ็D‰กฅโ’ย(. ฐŸบu๏;n‹M}ัม‹ล?ปœ‚ชžไvสtžษxํ๊Fป์ฒ{ษ+ศ–ฒ๙`ข ื=Ÿื" rPฯ€l˜D๎Vฬถ฿คฟฑ™•ป?ใํZ@๋ขHไฐ…]บ[˜ฅ3ภ๖5€ฬ%O จผ๊)\^„ัๅ Z;๚˜>F๗บtf›-Iบzำฎ €็Œy๚u1u™o<ๅ:ษoa:uq฿‚ีw๒ykk โ‹œ‹}0?jvฒซX+ภ่ๆ}Vป›ญ๏ไG$sฺŸบ ?2๒6๙ชฏ†ดYสI5c‘$๓Cfb!ฏXค*|Fฯิึ^บ$pบ7๏pไํ55งล฿ถ6[ฟmตเjgนจฐฎขl>*๖๑ KO& ฑ‰8๗ข:วฐ…oฟ๊ึk๕ข‡Kๅm~™oพSไ-*4ฅEผ}P/๛ฺอ%  k:กeื"ๅ1A้Jˆ–่ยโCAXšดน‹Š8= Lƒลข>ฐฑaฆๅ—v{ไ|K.3 ฦื๏€ท:\Bฌxวคw๐บๅ˜bษeb€ฯ>ึ1ฟv‰Hล?ไfน›58ิ๓ †%อ6›$ษฒ'pขL^HฏโXฮbpI’Vqnึๆฟ้A้8ฅๅK”g'i‚!Uz†แSEšชIื฿๛๕5ู๗N=๑ป๛hpอV?ก›(์Eล เšฝฟพVฅณr๛อ?ด้ว7žฎ๕หVฃฺ‹‰ษฟโต.ดใO๘น;ฐ๗•ค ัpค 4žN้RZm.–Oิ๘> MuพL'ฌษj5ฉชโำ`;ดMt‹AQถM„›๏๔y๋Vํ™๋<`‘’ $m)yšคฺณัX„™Da๎:ฬแชšqป1J๖Fqณ15คไ-lธ่\ƒ…3‰~Xกๆ-2pFD๐e‰้ึ/๑f!ฏ่2ฎฝi็:เ“=รhเ{%{t…^€ถ *ฤPˆˆ…๐Bอฝ]๎ฮYำD3ภุjd ๚ำึิ*ๆw|โ๊ผGLฯฝ}๙ห‘k7ธร‡ฯ=0ืไ6„oขz*ญžถโzo‚๐1~J็w00Sฯิe“Pw%ค”#@BJB ภ  %ู๘+„’ 'ธฝœภ;ค%!&ฉง )๐Hศq ๎7fษq๖H.งŽๆฒ์ะษ็!ุEฺวŽfณ์‘,9ีšƒ$9” ๆH{~iž€ ŸZณ๐)O|‚!"‡D.K‰าQ a2ี %ฉฃš2WŒฅษ‚\๋Ž{้*™๕B{7โ,˜9.๘'ew U^จƒWฏ&$ปr9ผต็rcG๕B๔็wlฌฆ๒ไ’l๖<ั๚พ™สท๎SQยใ‚…ˆh์! iฟัจvใ๎ฮJ :ณY?๑#๙•ธ_ำm4ฒ๛q[๐‡ห๛๛ }ื,ผE๓๕A{VๅะŸบˆ‰œฎฮP|žD๖ๅgฉ?9MฉลวId?{ค)ฎ๛ส/ย๐ะ /\[ ˆซJ๙าƒน๋[เœf4G>หมฬมQ€K ๓^  Œmืโน †ฆ›O—ว๙ -7w๊]หฬ๊ย„๔<U3jฦ,ฤษไˆ:“ฟY“ตqล~ 0ณ™/ฅm‘ฌลต@CŠ—CF€q<ท้’yคxหh๚Œ๕ฤ\๔Šฆ0=—RgY์d‘(๕ผ(_๐2’ธŒุษสaณ‡_ฤ{pทM …T*ก‹0žUญ”Tถ˜ู!ณif$ิŸิ(WโคqฅRC:P a3=bฒั rK1'-ป{ อ๖•H่สฝH1สแ'`ูkฯฏexข$’ผ.นhŽ{†`คF้ค z›Eร0ฎ๘c5xfMัฤ†ไพ}็฿พS๏•SฆฌœยยKล]N๋f'pPฮนSง`BmmHาv9ฤ4แˆ„^์m D $ก˜Š,€'„ „ p์Wษญ๎gุdV/Lถ;–ชืMZLญ๑๕๓“๊ญตขH>{€,฿รซทบยฮ˜ฑชใื๗Ÿ์ฌทฮ˜QSo l๛๙sษฟh๙?Aฟˆ2qชำะ`ภ5 ƒ€œZ€&*๊“X1L5:ู6ข๋ด๖ฯ‚+ู฿๊O]ueทj๕ƒลจ%?๏ผ&ำุัaW?{๏ขหญห2[}ษาW?ฮJฤb๒าฮ™›ฅkฯ-\ปb7‰sI์ฌkf&ฮ›f๊นx~ฌฟทn์™O-9๗Vฺ็ฮ ”~cˆW"ศ—y)b\)„2MrWฑหfˆ;Mํ๓U๋7ฅƒ'[ฏอภ๔น’-c/๖ด.พุพ”›ๆจuูM่lŸ&๘š.ำ๕9ิ) G๊ัฺ!ร!W* ธ60CŠั„#”qฃล็Šิ๘rqŸOศKมZOฮWq๙,ฦ8ฬ/Xpใ๐ฯํTšษศ‘ฑg<>ยยค)˜‰[J8ฃo` ;๚ิS\ำSะงพฦ๖“ภแ“%š†h~๐ฬpฬ|JหพF~K์=E0N๎ธQƒX฿วฉว*็ั็8;D7๖Q๑ั1ชตQCร% *E‚y๋y}ƒก UG?>I`ๆ>ฦ๊๗'ส6<ž+ƒํำฤ3IV๒gีฯฎyOŽ๛•ชQ$WBvฎH v๎…ข่[ฯ 2+ฃ ส'รธ6Nธ฿†<๚๚žว๎ษ•ถฆ— ‡2”S๑ๅจš9ณX†1 \•โ”ฃ๛ญฬdf>รBŽ~ถฒสีอ-ˆ”t>ฆW]ู์pฉPrœ๎Z[ฑฒ'žสๅศ+คฦŒโตl†9]๏8qใ‚์‚Cง้!Œถ' ๋@AAฏOuะจไช !?M\…JMอญอfวž)ซ฿•ห=๕ิษw?A•N>ฯ–ƒžหยผ}๚jQ<วpว ^ฮ๑๒(ป€}ฟฑฝฝ1๏„+ฟ“2วเq Fฒ๗4Rพ„ดiHฤ—๎IT๋r8าะ฿^ง™ฺ๙!gmญ๓ไ>ธฮดฮ'แ๗ฦฺธhัฦE`ญsฬŠoนใฮฺlพ๑…!๕(9~ํข ‹oะเ%#๐)~ฦƒ๚j$@€ี”ˆLpฝGๅOa{้ฎอ฿ฟfฮ์รฉ”)ฐz่ชุ”Yื<๕ฏ๏ุ๑๕~ฐ๙šฯ^๕Ÿcเาหšทs๓ะ๋ๆฝบใท๚ฒยดN•RUโะ›ีRิT”Y%8ภญค๒Kฬs3รqฟd]^ฉQTb' œฤzxฏ)๎Hด“FาฉP„mUฮZผjQ&œXน๑ฦๅoŸ฿<0ธjษYGผฑŽิz๊]Š‡ู$8cึฺ&ึhไyŸผ๕šอwฮž{ห๊9^˜ผๆมsf฿นๅšm[vๅีภ‚“รำฃ!ษ(ZํAsศงวฮyมBŠผภ•Œ๚8RiิฃBญg6ˆ{หUmฆ’tyW!bšpวฎd nล/yฬ‚แสผ@vลำ/ฉปิ%วcน—๖ชŸx๑En’:ๅ4Yร๕ยฒผ,yZ-ๆkrถ—๚cH&๖ว^ศฉฯCบ'ศฎ'^Tฎว๗“5บœ๎๓ะว๊˜r)(Š(I่าJ™UืŒ&#€Œ! +YM.J๏ซEX^|‚ยยL–ซw@ดฺพ์๒กZsg฿Yำๆดบˆ…\ชั๎ตx๊ิŸ ฒมฟxำ„ตyบ—L๏๕Cyo™…’<๑QสO$)๗W6ฅm%†ฎr†ีd๕”™‡ีฝี๐’{กO‰bpปAE€สŒg ะฮฮฒˆพคงi๒๏‰œ~บAคผข™๒฿O"mo*๎!ƒำ[TภœŠ๒mฌdHัT1ำ$… ษPิ4^ฬ๛sfcA3ท฿,ˆ๊XAญ๒PŽ๊bโks๎Yเ†‹ šyHˆhPไอหโ+b‚W=}ฅำ๛;ฟพ‚ตจ"Z&x<SySVYํึ&=ช4Ÿผ&‘่1Jไ5u~่,ำฟคz๏e๙–g^QB\/กPส„%+p‚re|Pnฅ คT’†cZ>?ข็๏–e๒žV"_[‘็Qฉ/…5Yใแ|ŠฑเqIฃ๖/\งำ9๖–ำ็ใใชdiฐภEBh$ชๅvา๕ ฑ€…ำ wOLค …๊ะ๚fpa ฆ,?H๓gH๙f2ฌˆตRb๎…ฒL v >UภSo™–ฐ^1/,šˆฤ“ขvcซฐY๒ Gm๔ภลจิ~ธAm๊zช ห?ว/ฆ’4ิฮ0‰‡yjฬธpแkฒ๎ถ2ซ๖H ซฦeE€R฿b้ลฯ/"M 7๒5uฒlย[ŸdrC‘&Yอ&I `!>p๛˜;ฆ๕ๅJ-bเย--.เดVไMฺล4>ฉผFjฟ–/๎5ภบฮฯƒถฏฃฒƒขt5}ย>C*›<'฿๗˜dตๆ?,cย๘พ๏dGf๋ๅผ2า0wใ6๓๓˜๒Lฌh"f๕Kไขื๒ฮถp;ีวีวฟฯถPีdจcฉ1พEO‹šัiพ%๖๗ษล˜(DCไโ๏Wฌท‰ช๕V้2š„I)ˆT๖iรM›๊ต๘FTzก0๋มชต๙›Uฏ๕ Sต7Vฏ mBW6;›nYZUขzSฯTg>(“h……๎F"โฝT๋ฝท‹ค๑R]็฿๛Lถ™|ฆ๛Lx‡[ีs,'NU|ฎ€ฏนEเ<๑4)ซR–‹Š p฿*ŠธvU#คgฤร*๑g˜๒ทjษ™*=~ƒจ์A‰Sฤ“ฮอ๎A J‚Hwไ3@Nurฎbw™ฐศ€สŒxภ}[ƒ`๐7บฝ’‹๘คZงหส›tPlh ีณฆL.)NU‚}ฟฅกkq'ุvล้๕ˆFQrืท๚Œ{หค๓S]๓Z๋L๙(ื@ใ*๚Sfย^‚–+u๖Pe_k#Ž๑•.ษ8้ฮษ‚%ี ฏ,…ช@•›ฃTKฃคั…บงŸ t`‘฿‘ˆXิAD;ฆ‚b†ค|p฿Aบโ7๐}qาฟ้2 @Y๛`~ฅ๎มตถŠˆiิฌKฝ๛0jŸ๗าYี( ž๘R„๚รำ~^ˆจาง8ƒ>…่=ใF"šหœA[ๅ‹ำDq๛vQœC๎ŠX๕|Z๕‹sO๗… \ไ/อf.มูีไณF;ฬๆkแP๑•์b‡dณz7ิeอถ-6‹bฒyถุฬbภaWjnh7Y๔LแF๛!ฝ4ƒภw฿โssFCบnh–ฬ_0๓๛…’> แฑMฝZฒซ ฐ๒‡€๏nCํœŒ ฌฤร*#5/O๖UัN\(3oฤ@…[7`‹Mg8xฯ฿gฌe;f\y๑ฝ—|fึคฉ‘จ]๋i5ฎจq5q&ึ>น'บ๔โ๓๏ผแๅฐั฿ู353้kฯY๊ญ‘œ฿=WŸ7็โ+ฮ‹yxIฮe<ฌพ“๛ยPรhฑX a๋๊vืธ‚ำ"ฦ๙ŠcJc๚ย›oHฬO†Cu]ณL5‘ฎซ†ิาลk““ฆ๐ั–†๓ฆ๕„ง]x๓ŠธๆŠ ˜~#ช;!‘)B58จ/Pฌํ ๕๕HšัF#0‰ฐB(๔สp์}ัFstาM|นฦl็ˆษ)]tฯผ&ƒ–™ฟ,ใ™—nt,ถh[ะไY4ฌ$žwQื’ต,ห ž@‹ัเฦkๅš`D”ƒg]rฃ™ท|๊Y}อVq’wRC*ิŽ9[oปฉ็งืฮd๐X 6Žš&อ=โอ}—ํ฿ฐภ/*อ\ห”)ณƒษ5gO๒˜lำฆฏฌุ}ูู1:>OŠYว๒s(•p6Ÿ‚[‚B/t็ˆ*ฬ -n:ฑฝ <ะฆ๐๘่)ศ๚ ฌแ+ฐ~q_}ไล๋oxt>Lฎฏ๏ƒV– F฿ศ฿Gผ@dฮ9ืฦ[<๑s/ผํฎ.<7๎ฑ๐ึs๓งBฒษžd๏B'ทwX‚œŽณฮฟไณZ้ตฃแWฒมี—ู>2ฒฝ?ํ2ศณฏฑ๗8›็ฌžีำ={๕์fgcsCณๅœีmใๅ –ฯrโพ๕e พ#Œ›‰E>ˆ๒45ตqo:แJฃึ์ผXซฐ^ioบ“P,x‹ตfต:/y ๑ผฏn9งV๓ัฅSง7=้บ๒๎่’u-ํ\ธ%•KๅฯฆUvถฮผ,ปโณ€„ํล๊Z=ึv›๛โkฏฟคNั*+_ง.ฺŠŸ๕ึžปišฺƒ=w @ฅๆlขm˜r๖๔>Oญวสo,Vิฒ’ื๋ษz &:'4๐ฮ5จำ…!๊ี9่pI 0@I[อPU""ฉsู๎Inv‡R>๑AธŽ˜ษ9tๆ$็จ3/ซšณ|kฃ8yดi๎ฌšE ๛“฿๘c8๓ยEื!Q่\‚} %Ašf4์ s*ฎA8ฆ‚Aณุฮ€>Dฎ=5uwถึ๕รบjณ๊nG z?2”Qช/I=๎˜fH่๗4ลnบๅ“]™ๆพ€ˆYm๐ชG"ณษ2ไPEH™อfนvZnž<ยš—PiA_าq/ณŽPษDีฟ๐ ใจ$$Ž~%NyhrOdM\‘-ŒmŸ(ˆไ@\ณบ#ฝ„ิ๊ฦผ“าN๏็JŸOิๅใ>a+ ตuJจ*(%ยขFP„JฤW””ฅŠฆฝ๘–฿๐‘,$)็๗)ๅ˜๚ณ’๊}ื˜ B\ญ–เฯ_ป๏๚วพwฅV้] 0†ัTฅOCรŠQ}่พ๋5ฑะยโ{Ho*ไ™;;๕่‘žษrวจโ๊๕Mิcๅ5ญใั4S : ดMŒ‹๎ๆ›7(kY:๋ฉชธ•zา`ˆgp ›J†stห‰ฑv'ฒกฆe๐าG^~ไาม–i็D›ป1šš‘Š6บdA ุ@'N ๐ŠๆฑณึญN.ึ๔?ฮfฒย…1˜๓b•ŽzJกฑD ์ฅV o@7R@6Š<ภ%IFฉุ0๊mj= [Ž}N‰โาŠค57นp๒ฉyŒฤv4@<mะญแฟม9Tล๒p?ฺR7๚๚ฅŠ0า›šดฺQฯGธ[j„ฌะศ฿ูอzi฿๗ทb“ด๚~ƒ๐/)wC?โฑ๏ ืจaฅ-/ŠCฎn“™๛.ฤ•H j63จ€’ะธŠp๘‘“Krh๋ฯฯย๎X–๊I็ฦŽj฿ ฟ‚oญ่1มล9 Ÿfิ\Ž~ฺ:-ฆิั“K 4ฉฑ7BYอ๖‰ฬ†Žy%›DC~e“่mภ@ั]ั๚%ภrฉึูs4Tฎ ™ลโฎะ๓ึG-โUg๕>ฯํ๛H‚OpVึB์ย]๔{9&^6น|ะm’๕ๅ _PLLI7ว’ฆiสฎ๕้ "'T }ฦใ๏? 4ป๓น›…|‡ฆ[Fวญ๚tu/ู_y;Z”ผ?ฃH†Kฎ0Wzค่c#คููะ)€Ž~.rมŠฤฅ+สB‰ฐŠฑ&Jฐ“ƒG 0๙ห[ะไู‘.ฮก์rทŸOณkŽ;VC•‰ oXจ ๚อ๖KS฿ณ๋rถt‚ๅํฒํ„:z‚X\ฑ๚xm‹Jh™xธ๐ะN๗วh5ฏ ฅK่`…;ydp.Ec›4ฒXžD<-ดllตip.ป^ุโpีื: ูu/๎ป๖™.ซลY[๏rlฏ_ๆ4ธšใฝษkzฮ$~Dq…]7/T_<่ตˆผฅใ4Kง$๐ฦ–˜ ™ป๐&w“ซ๖ไท †žฉSกธ7ต|K‹^Š๘›Žโฏ7ํMsMGแ๙็ฐ่hวรw”…นรใขด0]?ฒด˜fjaอ5aŽi–ะฆ่6Cผ2๔noย• ค“ีf ์=๑–)๕d^ห๛๖v๏ qNcใิŽด๛‹l=uูแ์]?;กfโ๙-Eี~ไ์๖แฯnฯv๘ี}5ธฯื%ปš๋พ๗๚–์่Oีปณd๋ฟ=„Z%vนŽ่ ำณะ nฏK— u ะฬ“*Jฆ„๊#1ไh„u1Hr์ะ oป}”๕๕SZโu=ถ‹๎wง;ฆnฯ—Uุ ๏‚ฤใไ•` F็๏ศถŸฝEn?ปฅ๛฿ซk&ปขอูl9 Y๕๎ŸŽ๚ํdซ†gAโร8NSGแ๊ณสDŸ09M‘AK{€K3Š„ญช[_]’%W4zึˆu9้\~ๅ๚ๅ๋n3ิษ๊~๑ษzir–„๑้ ลๅ๔จ“X3kโ`Psฤ๊๘สโ’nวๅ๏ฺ=m๙บม]‹ิƒJหks„อ๓Tำ9d™า๕eYN`}žฏ/๛]U#ฦ๓b˜;Rฬจ“ะภt,ขฺlŸh*๖ขื#JB+ (ดกiGx\}~IึณFทv@T๗หu†ึญธ๊Jจฺ ฑญ๗ฬœร @-Lœ™จแwไzรYง๎g—”๚ำโw‰`wx-๙ฟุ๖ด(dูข]ƒืษFฯ3_๗๘XcY’mQิƒ็฿Wๆbค-ฉะF ก๊K๕5ีd-0bƒโจ็ƒย—ึจ“T+ๆ‘_๏Z„xcหะj*`๛๛ๅˆ}|xโ~ธLวF*ฺS*o๊ŸMุช๊Aญอ๓–ฒ๎Tž1pว7ต1?‹R t>้๓ปถR'"๚‹ภไฦมEยyำ)oƒP7ž”ๆ‚ล%ซหภ$rรv๕ฌ QŸฟ๛ฝeE”วใุ็”+ฝปn๙zlๅVlFrktษึ'ตฆ'?Rฎ„'ZƒCEีIงKyป gaฮ๖0‹ก^แ๊} pE;…๕Kq{าแT/๙?๏i"%ํฦ1ฮ’b๑-ิพqฦ›ฬหตƒ+ ขŠ8ฦ]ภสrIธ๚”ฺฃV•{ฌdศชอœน\่•Aอ๔ำQะvOฤSี๕]0.๊๚Nจ์X9sนีvกb?OE~ฺFPU}ož[Y๎Kๅrภ้ึ๐๑Aนรฬ“U%‡ง7D€w ๊๖ˆqิ bแ/ํ‘hศ ํแAูฑ‘hPbQุ“JB8ีI บไ?แI%=ลX‘tOฅ;น(P‘hบL์dฃั S •'hฑŸ>|๙Tป‘ฌV?ู,O•็"\`ช7‰ีใํม.‹2ะ็>ฮๆนDแฒอ fฮึm•g;œ-ํ๖„ฬCŒ'œฅึ้uน,ปกฅฮ z—A`-วูผผม$ึx vc“รk2ทฆ[x–p\cฺbํ“lฮ€ihตsตœูiv๐aร›ร๊M,gฤจl๑ŠมMžเ–zำณ›7Jvิูภห‘V‡RWฯ‹Nณ•ไoฮ4‰(ฺ-„ตXB^าCl&Vn๔ฑnŸฟพมn D4[k6žณ†›์Nื&ตบ}fง“3YผQwะ@$“U$(วชoถ:-ZGผ…#&‹ล†/…} –?ฤ฿N}ฦฅผ‚7ํA!Mดเh๖๘W>ฃๆ๏?iXฒpส๙r›ผA–ูกเb๖๓?uฯฑ›ฮนณ-h๑ไุํ๋ๅ6;ป๊SB๕#/‚ต้@ัฟJ ฦ้ !%Qญ)”ภDq:{JI^‘หก๕PY7UG็ส(ยภผŸ๖กhณ?HmฌศัŠๆvREˆํHŽ๔็ิ=N`P)QœŸฅŽๆ€G9ฎำFM‡ึSแMGง๕@2คE‰$Q ต$วsฑ~ไ’TkNต"ื9ฎี†8๛c๊Fค๑^๊"?+Gู  ^๗*ฆผgUlFVxชฺU™poCจฐž.XCฦตŠืตอ‰ืq๏‚K๊[ฏk[ฏใ๖K—(lภ;๖ บำกํn่%^๑Rิjญ,$)ง ๘ทส์1‹‚n.ฟ฿G๗:Cะ๏fŽำ(฿๑,˜ํะ;š„ฤดฉ๔Rย—ฟF๋ž_~๐ฮใ^๘๘;ผ๓ฅีซฟDพ๔;6|/jGGSSG„›G’ำŽฤผDฌ๑zbR๏ค/X?ฝ๑๛Šรํ๑Uฺวp14u˜$`พ฿[฿œH47๕7I๒~ฅ‰~Ir๊฿™s๙๙#่Ÿ6Œ+‘h„บe€W๒6@wK“ฬธh6, ‡1Cต"เ‡ทๆฉฺ=ญmฮ่๑eฐA๒ำ๓Œโ– =พเ๔๎@“z—ะะแฒ ฆsา๓lsณ]๔;kklต๊ำrฌ^"sƒก้’>ี&ี„ฌ-[ื฿{ลJiาด9[‚ตณศฉน-็]ฑdขตฒcา ตAnตนƒไg็}๊’‡ผป6hT๏–๋ษ–ด?3sฺ^kญ๚ŒL๊cY ˆ1๋Zฒดn[๗ฅƒbดพE฿†Œ็ีคอwฌบเk3ิf™•ๅˆ>† fMไีD‚่…ี ธaๅ๑D๐้ ~}&๖ฌม@ผฃ5u gnจ›Oศข<นญ'` &bำฌผฑ-6เ๗ฎ;ƒมX๗"‘d*ฒaกwาY”ฆดvึิtทLณ๐ฆX๋ฌึธk๑U฿ฉ๘ญทa๎่ิ=HR_ร@ฆฃฤ+j“2—๖T*‰ยฃ่%์ำ/อธoฦค ฟฑำyแ‡๊ ฃ—ป๎›1ƒิ9/7›  ~ฦ7แŒด_“๙oรำ๊ึ+‚$DาฏsIH:็rฃ ƒ๐ คyiF:ๅธ๐๏ไv๗หภ(ŒฟdฎO":ฆ omุล์dM”8ๅา ;คžZ9u๊สฉHCg\›K/*‰Žิ™g*พ-๓I่ั๖—ฑขˆ_มEขœ‹Rq๎R'๛[ฅf๕?GีUฝAo‘vb A$e๕]นี/‡ิฟชฃoฉ?|ƒิQโm–4™GขŒƒ7๑G™83ฺ3+ ผ74‡z*)ก$‹JภุpDต๎“N๚j5pะqพท“eDf/าฮ>ฮ‰่ดเ”%้–รg‡Wต๘ธ{…U๓๋:g,๊nŠฏธlฒฟตU‘\Ÿฯt™'ึฌ๑%าัE‚ซ}เ๒อํuข๚C›่๊˜’อป฿บpฑ}Uห+^b'‹’ฏซo(5gใผVรBีIœผไOEm>ทัภฝฉ€5yšzg๖†}๚มฅA”ฉP-๘P/๚€ซ์„๒ ี6ฬ)กx5/t;1šp“1”L บ9๕Aณ|๗๊ั)ตแๅ๎X]m๛๓kFE้H/๑4}:ธ,oLMชo๒ฎป6]YใM๓5ขห๊0u[›฿yาซˆfV๊hพ˜?ธไE-Aง_i๏ซ฿jฒ๑ ิ. 6|ๅ5’`#ฯีZ-๗svมfq˜ำŸ›ํ๊sทอš>ขใฺw๊ ๎7C—ๅ{ A“๚…Šาึ]Bพ๋ี๎z,i๗H'd๙„ไแvฺ?’`E‡• x,‡๖mฑธz‡`อF[๏๊2a๕v‰hp™%(ิฬ’๖พส‚ษู5ิฆ;GฺัŽh”ํฅฒณน\y";|"ซูส–ฉrx‚zหs่P‰HCTืvจP$…้๕ly}‡iyhvMŽC๙…r)#รxฎ›ฟ-.(๓t%fuป๔สย€(…eม•UU–oโฒ ฅpดิqeัหกๅ•—ฅs๒yธiบ X–ๆkฅ`ษ>ฃXฏ@2P๘ฏ. จ2อŒ>ซn„|‘ส,/4—ฃิ}ย๒  ฎ๐?Aํธ&อJŸ†ลๆrง+ญ๐รษ๑CV“]{Ž่์Zอ0- ๚๙A=–ไ F๘ฎ$๋+”ำ๖๑%UธZyขญย—ŽฐูฒRี ทBƒบ)ฟ๗ฅŠโwT8๚ม(แaู๛Rมฮฃ*-ภช—ฦูs€r5v ๊!^tZ:/วK,'ฑย๊F  9€ป=ใฺๆGˆ<งธCํัu“"$บ-พ๎ฒF๗๓S2็(๓ษF 0Qฉ+Xš๐wศ,ป]=b๗h[qžB‹QI’ ง๚;)"มลŒšำ9า2ใš๔6รr?็ำ}lŠV ว=bธ[˜ตฃjฏข4€Az”๓™Œ๗KฺkQ?Tย[%“ษ$ฐKขQ๕-ฏัl_@l/ &;˜ษ์ฐจหDrช?P_dฃE1~z—โ^Iบ~bฐ๗งิr่ผeกuŠผfนญP•/อีบ# E+šS\์G‡-ุR4จซ ‘Sฎ๓ึSฎ๓Vไฟ‘; ฮศ*`฿Gธ้*5'วไ๛d™Lบ ˜ฏ~คฺฦ๊ เ…5ฏ”Fšาฦhb`  ฝ๊œ๚ณย4€[b$~ฟG็ฃNฯAX$๒๗฿~๔‚ }[ป‘W฿}ๅ๊ซ_ม‹zร—6m๚า&~O๑%ๆj/แrอ&|_Sˆฦาyใ<็-โ*Lฯ›๘ซ,ฉทJQ›zปอคœ๐ซทฃูยษ็ั|’V|GVW~๖หอz ท ๊จ เH ฏœ†๓ณจEษ คบฒY๎n‘ข็H4๛r7P?99ญฮๆ฿ก|O-ตท5ณŠ ฒ%ใ4ต วณ๊O/4๙L_Psๅ’Tิ>ฟLQ›ฤD(๚ ˜ฺ๔๙J8๒Fตฯ+)jCb ›Muุ2Xc8$๑tฐ}œ&ง@€ซQrผ-ฺึคฮU_o6วีหq7๕P1๊Žหค+ฮพrc6ชI ๋\ ๊(*vด2ผ4Uc(A ฬฃ9๚3๖Œๆ]วz๗ุปญ;0'ก=ไั*,e5ี6ชปVิa,ฬqhฬ*๔๋ฒP@wศฌฐGฒธ/ำOึj๗|ฬFImว #Pz๋;JจwสŽ}ž < ‘๚ Ÿภz TŽ๚t‡ˆ~ฃ`ยศฑGP%;? ฎ5((uจ”# ”วศvีŠIศ๑ํ#9,?G๙กฌb4K]QgิŸ]าE[เ phสฏง‡G›กเ+` ฤฤ˜p ?ล@แ>!ฺ}" อาฝธสr=ิCภD5๏ 62พฆZY๊่ๅ?เื ณึi๐หAจ‹ T(๘E U•Juณ;"}ฉุีบ#–๊‹Lˆcจไส้ำ—ฃVไ๒้ำWO๛›&ูCIิ™๚วu8*็ƒžQๆ็a๐Q^*z(จLญ|Jำ‡ฝ^Žfฉp1ฟ๛๕„0ภ4~œCˆณUxจฮ*ršVฒ*N9ฯ€„ืณฏP๐๚„ลซs๑pถˆœ_LŒ‰ŠŠ๒อแ3ูZ"}ˆ&ำr๔Oฟ|l๕๊ว~ฉศkฟCพ/Wj><หSลxิภๅM๊bS“ฏ–—๚g(]ฝJ(Z#Ÿ†xฉ\$OC6ฟ8-เf:{๊ƒSาณ่šจo4:œำห)ŽฅหWbผ"uลiuทhŽŽ~ฝdใส้์%๛ฏฏีBฑ๛๓AM sอโูWH.gv%๙็4–ใv๘+ธœง๐=ยฟ ๐ีS๊G‡ฯ‹jWHWภๆ็u>…–[B{[็u๛ษถsี;la›z›i๑ีW฿ญิ\zฝฦๅC๐ไƒึ|ฅž\fทซŸืteฟบ&น฿•+Bkซ่/tฟ ฯ CM„ /@Sร>Tm ฑG`v๚`?ชฃ๔ูGฦ(ู,zb" ี็๐eถธAืAiเปดš7ะำ้QมจR<ฦ"i X ถ:ธI‹(a‡Vพ๖œ๚ใฆห็ง;4R้๙—๓]}—ฯœ์^แซฯ1v๎ิตฺ๙—7œิส=ูp๙|ง[Jฮฟœeํยต{)ญe#Œƒief0๓™K๐Jโqฒ"*๖F#ฌ(ฉธGjJFhŒธ‡X่#ัˆทโตฃ๑kšพ—5EิRคPอฮ‹ใ† ^p Cฉeo๊ํe…€:•็ฏ{6ศฌอ๏5ิอs‘ลฦ™8‹Xฯ K6ื๐V[็=็ู}V+hองืภ฿JŒ›lัโŒZZ›5ศ฿W‘‰ฑ”;ฎT้†eซV-@ฺH†๊I๐ู๋ฌฮD<อ™[ว)ึอภ฿ฯึl^bรXe๒ูNNฑ„ฌ"K]ฃ@ŒƒืbŠฉšห?.ๆH๗H gzXaฦ๐ะู’A๎ˆ}MOฆeXยHงNr ะ๓ฺŸWจ;๑ht๑ปgttO๖yu3=–ย*ืค๎ฬๅุฟ C ยFGsh9J๎อฝZฐ-”k‚’]L-ี~hฮiiก.๊49อQr5ฆ๑ฝI,V“…^jf”ป_}ิ,“ํฌQ6?ฬ5ๅNV็มฯี žหชYูœN›ๅ%ezหqฦจ๏>มZ่  “Nt๑Šำ1š a ี%ž=่ yฯh™ซซ Hธม—หJZ๖š? hฝvrœkสม@ๅmอY`ฎ^ins๐๊F\”*๖|Lœz!/?ท)(“0 ษโ MS4(šศ—h๐ุ{ฒบ™’ๆ๑-๎'ืhŽๆ‰๋o๊7๛cCาžส?‹6ฒโ๑’ฒ'|ubˆีฃ@ดฬ!ณbร™กกปะf{tz๘…1UาA?=Œ@œแแ t%˜ไ•‰ภฬๅiuš“[ N๒ศiแDย ฑˆGT@Š:ฯp<(๕cำX้ษฦUm2ฺฯฑ7z›ฺO๒ํM^๖FฯดYUfwGsม“#‘t:ย/‰ชƒ•ถฮๅ~ฑOs]ตวFืฯ็‘ฏž(๚(^ศ‹มฑ?L๛$ภSสฝ… WzT>m์'_ง‚๚dŒฎะิ็:ฅฤ5ฎLh;ฏH7Wgz๊gฤZฦฦZb3๊{2d5Jjฆฤ9c+ใ๙‘\vqz็ฒDbูฮ๔b ฉฦถg ๙"l@ืฆpๆQBฝbS Qํ>€“+d pฒพ๎%}ฏL!“™ƒ‡๒็šcdwHo˜ธฦๆืp€x(T์พpฤ่x๎ฟ฿p#๊:dvQ qŸdA๐QFdLณฆKคmาPRหํ คpU?๒lร๋ึ ฏ๛zgฐ-ช‰ถ…ิjPู๎ฉbท๙GืaR๕ฟ๚&^qลเไ>uำ8ค•p&รำฎั„ ค`ํMGSŸฎกฎ๓ูตaoฐ๑ใกW›ZดaรขฬูŸฐทV5ŸสRฟs2NX qGB  พO “สKาg ้๔๐ภภBW)Sg\๕ู๙๊žำกlไห๗๖]z๖ภ<฿ฒo-_ๅ- ๘ฑ›ฦAฃŸKMqำญ!ดรฆSiญgy๑ฟฤฐ]K๛;S๊ฦT'ใฉ๓kPqษๆe๊ฦe›7cZT{~*‡7‹b๊\H…?๐jูตl3•P œ๏ะพw๏T2ฒ๐jŠY;ึ)บl DหueytOT๘๏ฺjรถก๑UถHญํจ๚œX๖gษฌ,๕Wดษฯข^Žฺยuถก![]ุv”F่็”| ใฎQGแฌh`(จ# ƒRผ'5XฉDงฬQ สqMห6g๛c'b๚uณ:'—๔™ฎH( „?ธyีตฅ๚ถห6ฃ~.๒eจฌ[n ธชŠ*€ฑUซyZsรt 9๏‹›ธR!Gูทฉ๙“MM$ฺxŽz€ฌ$]๊ำ{ะรL<}๙็4ฦเJZี๕๊~าMVŒี•hyื >@uปสํ šๅ…๎+ณฟฦ่๔๕]แ฿2FqO8j–ัฅฐWCปQqํหค๋rwฎ‹.ส์ไ„ซพฅ\๕š_ด๚งใ๔๖yšถ\Oฟnิ)IอKGRง๊Hลq”ธIฺัล. d+u@ฯด๕ำ ๊พk–ูลค}9ฅ๊Tซv6๖*xคgถe7?—™รฌิ}๕Sง้ี-๐ ํAU‚ํ OMlJ ีpํีชง๎ีŸYw–ี้hœi6๛\fA๖Zc,ทrjFœTๅ‚ะMj8kO๋ซ51‹TปผqWฝ_ูnุ้`•7ฎ%ณKซ่้Ws–d0š‚แ:ปั`ดถOX•๙าs$ใ4ผ?:SI1ขขW-พPr}๊ยฒฃŠ ง9๕.ิ๛& Pš™^f ณ8(บWกIขร`ฌข`@5a}ˆzต๊iV พp„ฒPิฝ+:–ฃd\jร"=a€jฃŠ๐ญไ)Wงิ$q๖{วฺึอœืp)—V๕ฎ|ฃ7hjฌŽอ๖โ—ณ๕ปฅษโ$ทL๋่˜ึกส9›\๖๚Žฺn[ ฑคk{lG‹‰ฤ.m „m~วT—ภบE‹๙กัbศญถmน` ป—w๒nyๆP&—:PฏLJุํ–YŽ๚้ภŒี_๎p™NWจ‰ž›zV์Sืƒ]7ึีEฺd“%iธ็™ฌ|๑ พ๚ู่ฬEิWMม“™7rโ ภHBจดห6`UGนZˆๆฮ ๘9 N2l2ษ…HY˜ฝ(šล—าิูiwœฝ“[`ชcZจ–R;Yz=TrาvH9๋c. กึฒ๊GไŠาบ6ป*pีฮ…'ป[โ‹:๚/˜าชXุฅœดCYั…M๑˜ึtฬะ-'บ]ฃn,{@๐ c๘˜ObธๆยIัN‡.”xมN นF9๋ส๋›NญK๓฿[บฃXำr=๔้Wฮm๓ ฐฐฦฆุY+พส?sJ๕ขฤืgฬXตuหPนฏ%ศ—V^ฟ้่[ญ๙‹ ทท๔„ิWˆ ฏ;้๚Wๆ ณxvฑiศ/‡ืXS3ฑๅศผ”2ฌค๗ิฉZพม fื2ิๅ/y๒ษฯ?๑ํปไ8๐ฎM@ำQิฮ*ห„ยศ๖กกํCไX๙kข๗ฒ?MzTy?ฑเีZหรYuืณ)ใ้]อ•฿๔1–-ใ—a7j๔‚~”อฤแ .dใฤแ ป –ฤ'ำทฝธšงบ“Vิ๘zฃtXKู2kฬนd?ขงz‚ง๚zฅดูKŒ.บ>,กธBZฆฟ`q„—'บk–H่qyข๛ฐๆค5ฏพj>aวลล\Cูี#ส็H;#pว๘ฝะ7lบ4ฟ}” ึIRธ7Œ„ฐŠ0ง๛œ๐$โ์=ซVมฟ๚ั๖‘ํ#์ธไ_.ัvช…s๐{g>ƒมh!ญ๎ฺAทbน/p7ผะ๎™=z—มฉพmi”%—อŸš3)^Ošjผ<_ใUถNY63dsIrฃ™ด8E—j๑ฌ฿ฎU๐ด„*ฯ 33ฯ|v พใ่;ิOฦ้B@๑๙,—,Ÿั\cwd}6k.žuุkฬF9ฑ'ฬไ2ะ6D]eฑ‘xGพJณK.ืฝ}†๑Sซ†$ƒ@ t"š;2ษฉ๊น*คŽ”Œ4ง1_ƒไx๕7ณีQงbjดXˆฃง฿9›„งฺQ๕ฝ;ฎ#ฯ{9†ิeผI š-์ๅฅbr B<ึ9๒dpzœIVช๛๓Q:l„+๋s้i ี#=ั์Tฌ๐ฌ+R˜ั(ชโMDC$โ นสa็ฬฑ ๊ONgฤj1ž9˜ถ›อฤgqXk่„}Fซล่๐๙dขคืึาcถŠG“,ตๆๅ&—ำ.ุโ˜.^ษทwwcƒ>ภEด_]3๛žUˆฑ|ใt{Jีfช็ช‚u_๙.บ\บ*๊๖ฒW•=ฬล}ิlN็ีo+^ฎ๏฿Vฬฃ๎้‘ vุPฃ>~†‚sขฆTถjWz~_ถคo‡gSะ}-ญD๑Td‚ -TภAaศ๊Yfตฒ˜ว3,PATcmฒฺ ีผ4gใธ}‚ฝ•mเE$BŽ„wขลช8>ซฌ9–ไ‘์ธJWโฉฐOฅ๕/9PชJCษXA{,™@c,tEJฏหTศjฝพ9์•8Qส๑•ำ๕&จ ไHŸ Pมl~K%ฦž๙1€พัปน -่eศD zxN›ปXuz’.9ฝไ}‰Mยc&œ:ฏ๊”Z5ฟใำ™8ท๘% ีตีฝ๘mณ˜ผ๏om๕CB๖:ะไึแ‡lด๏˜8™ฮ฿ฤํค~๓หฆขEฒฟj•Tง฿มํคYH˜Yแปv๘nƒV^IN]]ยŽๅCXkg#ลs cภS๛ˆB’$‰ร=’$ชk๘}cGน&๖๗/จ฿z็ป฿}็ไฟ็สฦต_๎ัv6<7ธฝดIVGG™๚g*l๔Ÿ\RXSฒT‹‹ฎ)ูยšE๎คส%Y u๔๓ม~Q~>Xคธˆฯะ…ฑึ`9ำW‚“k*‡@_ีŠpMธ]0ฆ*ƒ%ใaภ“3XํŒKนM|๔{†ฃFิ”ปƒ๚‘ ์ทพd7[ีn๒ฆีl’อฌฦD‘ฬฯ๗@คฺm๕˜ศฯ๑8‹Ÿ›ฐe ฏcลผ๔#gH›ะฤdd@~.Šjฬlรlษ›ไeRcx๎Eฬไ(( อํ™Kž™ฯmยผ๊๏G๋X”A7พืS๕ทต@[lฺื.%ศ๋ๆีฃnMDsˆ]n€_Q๎ท• ฺ5ภi?z๛ิGT่G3ฒฆT@e ่iดื,ˆƒฐrธ O2<ฐะ่•้าl+ณภฐ/,ม–%‰นญmฒเ ผำšXุn›|–Eš›ถ๗]˜Žˆขlรญ™๔œžฦฯ[m<’|#นzื+„5ˆข 7&\5S๔-˜{AE฿ื^ูธ์ฌt“Kไ ยหฮญ฿็M๑ฉ^rq]‚๎FmC%2„vJา๐)W-ฮ}OM"`ี9lฒ+์=…%"ซ็๋ฃ๓ญT๒˜'8ยzH3Qา฿๛ัฉ๙Y์P~Vถ‘ุฒ่Ni์ผอ 7Žี๐ฤ›ฟ ๋ฮ?w1๊ืxฺc`d```d๒อ?ฬฯo๓•AžeP„แBY๑t?๐ึ;ฌ"@.Hc ่xฺc`d``๙฿ &]aฝรA‹Š_xฺ}SฝJAžีS<‘`Œฺู‹ซ ‹€ฺ๙ยb)6โ ˆ>@Dศฤ"่X\o“ !‡ญŽ฿์ฮน{ว้,_พูo็gg“˜gฺ ˜น#J†VYp>uC4ำ&*ู<=$Œ์๐พg9ำW@.0Ÿขq๊๒‡๚- ๕๑๊๑;ฦ:pt"HUๅe่พ5ไี็ Vg(๗[Aใxญ9ฯ!ึด๗EMŸ๊฿—๏4N†&ำžื๐wjณt™ณิžeฯƒ่LpŒ>๏Š‡†w‘๏†๕>Gเูpfz`อ|ดโ^ชaูลผ๘>ไŒว๐ถ)วoฉ oฅฒ‚Mg+RŠmนRqัํ,๗ซ‹RJนเ1—ิีX‰TชไN7t‹{I–E—\๎F๋8ใU บษฬmbอ๙:f—Nฑ&’j9ฬYxฺc``ะ‚ยM /^0๚แK˜ุ˜”˜๊˜ฺ˜ึ0=avcฮc๎a>ยยมbฤ’ฤฒˆๅk ๋.ถ"ถ/์ ์I์ง8ไ8’8๖qšqถpnใผวฅมๅร5…๋ทw๗)ž^-8 ||||[๘5๘—๑?˜ ฐJPKฐLp›—…Pะa)แแ "Z"WDmDWˆžc3K ๖Oฯ~•/‡‡cŽLŽuNN+œ9K8;9—9/p>ๅ"เ’เฒฮๅ›k”๋676ทท-n฿อหทน๓๓0๒๐๓ศ๒h๓8ใ)ไiแ็ูโนส‹ษKฯ+Ÿsช9ง@.xฺญ’ฝNAวw ‘h ‘„ย๊ /‡"‚TฦD๑#J$Š–r๒qr|่!'‰Oแ3ุุX๘Fมงฐ0wY …1fg;73;3ปยx†๑E0C€ q=็ฎหๆqฃXว4๎ฐ‹GลA$๑ฉx ืZB๑8ฺƒโ Dตwล!๒‡โIฬ้aลSไธโ๙X๑ ข๚wฏฐ๔.ฟ0ซ?๙พo–Nณโุบgฺึั@\ยAด`เžsbˆ“ k`งจsก}›,์ซ0ฉYƒหa D๐ไฎศตศตMyFหMvแYdฐลS๗ฑ‡ํ2Œๅขฤ้0~™>ด/ใŠqJŒžG i๒๔<า#cŒด0๋ทC~Gฒฒƒ9ee Kvซฏญะฒ[ฺท{&V(ำจ1j•1…M‰Zqr7ฑ,gKฅXๅ่›ญ๕ๅ0้ํ๒–›ฬQY{ิ ช›MžY˜นะถz=ษหืaฐ:[jEขŸ ฌฒ BZ์Z=n๔sธ`อ+oอิฬxฺmีU”SgFแ์ƒงB]๓ษ9I๊$uw๗-J;mฉปปPwwwwwwww˜l๎šตฒ•‹ณ๓]<3)eฅฎืฟ7—R้^ ฮ่V๊V๊_@๗า$zะ“^๔ฆำะ—~๔gฤ`†0m้[ฆczf`(3233 ณ2ณ3s2s3๓2๓ณ ฒ ณ‹ฒ‹ณe‚D…*95๊4X’ฅXšeX–ๅXž†1œ4iฑ+ฒ+ณ ซฒซณkฒkณ๋ฒ๋ณฒณ ›ฒ›ณ[ฒ[ณ ฒ3’QŒfvd ;1–qŒgg&ฐ ปาษn์ฮLdO๖bo๖a_๖cเ@โ`แPใpŽเHŽโhŽแXŽใxNเDNโdNaงrงsgrgs็r็srs —r—sWrWs ืrืs7r7s ทrทswrws๗r๗๓๒๓๒๓O๒O๓ ฯ๒ฯ๓/๒/๓ ฏ๒ฏ๓o๒o๓๏๒๏๓๒๓ Ÿ๒Ÿ๓_๒_๓ ฿๒฿๓?๒?๓ ฟ๒ฟ๓๒๓fฅŒ,หบeณYฯฌWึ;๋“M“๕อ๚eณูภlP68าsไ˜‰ใGE{RฏฮฑฃหๅŽ๒”Mๅ๒ิ 7นท๊ๆnแึบp;ฺ›Zํอ[›ฦต? ัต•ึตykxื~yัj?\3V+wEšื๔ธฆว5=ช้QMjzTำฃšๅ(ปvยNุ‰ชk/์…ฝฐ๖’ฝd/ูK๖’ฝd/ูK๖’ฝdฏbฏbฏbฏbฏbฏbฏbฏbฏbฏbฏjฏjฏjฏjฏjฏjฏjฏjฏjฏj/ท—หํๅ๖r{นฝ^n/ท—+์v ;…ยNaงฐSุ)์ิ์ิผซfฏfฏfฏfฏfฏfฏfฏfฏnฏnฏnฏnฏnฏnฏnฏnฏnฏnฏaฏaฏaฏaฏaฏaฏaฏั๎…๎C๗ก๛hQN-ฉฯี๖กะ่?๔๚‡Cกะ่?๔๚‡Cกะ่?๔๚‡Cกะ่?๔๚‡Cกะ่?๔๚‡๎C๗ก๛ะ}่>tบ‡๎C๗ก๛ะ}๖๔๚‡Cกะ่?๔๚‡Cกะ่?๔๚‡Cกะ่?๔๚‡Cกะ่?๔๚ฉ๎vํNjHMp“[qซn๎ฎฯ๋?้?้?้?้?้?้?้>้>้=้<้<้<้<้<้:้:้:้:้:้:้:Uฆ>๏๗๋:้:้:้:้:้:้:้:๙=้;้;้;้;้;้;้;้;้;้;้;้;ต}ท๊ญVฯฮhๅSฆ่่๘ทชูoTPรZoneMinder-1.32.2/web/fonts/MaterialIcons-Regular.woff20000644000000000000000000012641413365153155021372 0ustar rootrootwOF2ญ ๘ฌฑะ$6าP`Œ …๋D„ฑ6$  ‚z [U’Qฃl๛D‰ ผlปึช}*lDุƒ_ ธ1t3ฦžo ูRr0†@53 Nุฅข๕m๛6อZฆ(ๅจซcoh‹Œเฌ๊ซŒŸ\฿9็ฏRธuzฯœๆP ‡ Y˜joใ›Zl๋ฎŠciK[ฺา–๎ ฟ'G W=^๗ห—ZฦฅŒ*?ๅๆลFฐรdํ๗Ÿ‡ฉะ]pใX•C˜bฎT':"ัnฝ (วๆิHฺ๓ไKูUฝ3ญชjLNฯ "(I DB๛ฯญฯ,?7,y’ห๖VงP_Uำ๑kL…ฌีnฏแชL!ะ Lp4L–S๓W๗R)1ฟ/QๅyA๏{๏gะœJ‘ัฑ{สsyฑXเฐdkฯผ$/ฃํฺ๖6zšM“ด]w[8Ž8qŽ7.ๅลˆ˜Œท๊คฎ=ฒอ๋ยถจห•ิ๒jฉงjมtโ็2—ก:#ง6ๅ๖ๆๆๅ„EสQ Iป_ขžฟฺoฝ w๕Y๖Vก่v # <ฬฒ…้`|ฆ$\฿D’˜๔๓ผn3/ไ#! {ร‚ ˜< ฌeฝ๛ุJขโ`9๊{ตVมถึถ h๋๊ไลึ_[๛ล6tLฐใ’ฟm‡๘ฺุi็ฅ:\v>ไฺ–R‰pใ,ม{๙ฅฆถ‡๋า๚dั๚-X๋"ฮw†   4ašmUภ ่Hฏท)๑€cC8NตจสTR๗f6ฃjห-ฉ-4Fฬคฝ๓ใsW:๋ื์ญj๗ึAi‚” v&Hย N€ซบ0ฺ„๛ytนV_ป฿Z-0ฬdŒเ Yษ๖ค$ บ{๖M^6ไิg์3าวด]YjปD‰ฒŽ=D—:jV—ซ}ตฏ–ฆป๎xโ๘ํ็…็๗-Nฮอฟ“(Œนอฆ๔}yk,ch@ร้าRP๔฿7ตl๕/RW-ฒœฒŠฮฑ฿ถจฮE้ๆอ๛>>~pวs ์h€“(โ–ึ †œ ฮส†('rDศePา].J‡˜ห+ป๋*ป่\”.k๗†;ueพnค๛ ศุD+b4๓ง ‚ยฤŽ้€NOPOะฯ๒๗ฆx๕\กSขัz2ฃำjด(๕ปเดฦ๏]ฺฯ”ˆ ๙๛Rญบ"ฉฆšlKี8๕ฬ๕Zี8s_๏Nsธ๎แ’๙วOไ๘ ๆO&d ˆ ’ ซ@PT1 ˜dw€Uพ7TฎชbVญ๎cAPฌฅ)ีTW{Zgซิช1ฮืŽsœฝž๗|™รqOื…mนง๐ฝิ!bxˆ2๙ๆN—ไ'}วUJ มa„V!„V“73_{๛}ฏ{˜™๕ฟป๏ั"""J”(ๅ(%โฏๅ๗B™N:—ชBE}ำj^M๙YฎZฺjีuoชuคฏ@!ผ๒B $<3๗#sำ;–ตุชUTœจฐณN’“ก?+Aฬจ๊^คฏO=ช๕โvDฤJๅณฃอ ตŸผะ‹้jƒeล5^tรซะ๋qŸ|ุนญFFด /Pi˜E๛ลฝ;O,Puhเดฐพ่P^ :oญุ8‚ร฿u๊ญ2Nm5vm๑œ็=อ{?(a}„8DaxT#ฺ4๓\อ<ค9ปuท๊อwZฏแๆ)ฮuฮ๐ขนgKฅAXeay0฿๐ธศSEย„’ูšsแW—ผpfp&QถDขฆ.“ก๑ำUซญRฃN}mฌคล:๊๔ี 9ŠคขTCL4Nช’ธ€Jˆ*<จ]๕ถN2้กQจภะ^cทฯLmช5y ท:๑ว[g‹*†UสำชŠ๊นฝNž ฺฑฑี.็ืฌ" ิำ3ฬ(%ุwrฏป๛ตvž†ํaA›=U•aa qุ˜hฬจ๘nว{Wฑˆ=\ึ.Q–_^}“ร–2Zฎ&pื:o(ํซ DŠุภฑถศˆ-R฿-S๙fSkรซุ๊…+ก*gซขwห๗E`ตXS๏ๆ8šฝj‰ท6NO๛ฬ“`k ห>bŒ?ฎœYฟษซ&Ÿž ๗ถะ#yฌoบิŒhWะ –ป =แ=Rาs๎ศvุpวŒ๘pY9™๗‰“ญำยฟDm+ ะ_๛ฑ‚/๏์2v06–Nำ0r‰I๘˜!ภ4a•Oฃั[u!โ!ถฑxฯ!งuวˆภ Vตจฎ๔๑h+†฿ๅM๏อ—ae3มqe~ณwฮKีƒ๎คึล์Tสฒ.แฝI:็๒ž!K๏oี้m hลชฟO๋99Bp' Kำ๘mฬ๊ฤฆแ„š ๊ะผ่ๆไ }ภt`T—ใํฐdTwำโt5/บดl\ฎือ^U(”m=ตŠ.—w๖–Šฏฺj๚า์;ผสŽ๐‚rY ฤำฝกฏ๗ฅ๕ึโะ|wUT(รย[)%D$#nขศผfoฟฒฤบํ๐Z*ซืM9[—ึดณ฿o™$งฌm฿Kbๅ^3‰}=/<%;่Fฑวฎ๛fiคhatXG?ไอD_^ส้^Qฺo^œอๆเ/ธต–Z'Gn0lbฯ",– \ยH๐~ั{,#ำฃะ'“—Nw+gx๕!ƒƒa5ตคีบrวณ2คqบ”N็ฃRr>2ล็#Rt><…Uึฉh—x™สจŠฅยศฏีห T7k|Ÿศ่ฤ_Xยฤต็๊่๛Œ:ุ๛zีGUฅ(€5q2[%ั*Rjฉ—ฮั1ฒ:ฮGซmญตแcav‡gฟ?Iฒััฒ8*์ฐ ศŠ?1นt่QฮP ่NˆS ฏ- š7]๊—ึ๒i™ิฯ  C>น๊xK๗ƒคY;VI(เUั฿ณ|')ํ8flๅฤ)v–ุ๊iV!T\#2ศKyZํ&**ีฉ>ŒฐMq๖Sื฿hู๐ฎๆูฉDฏ’ีTKŽTUD$*กTPa๘ž›-†–]=ฃ~;‰นณ๔r‹–*I„ล—‡„†’•)_฿ะฆทล•ktท็…ลiRๅ ส.„LL9ฑโDๅœQรๅ:–=๙)ซŽล๎ฦ\—„Q๓2ยฏฑWG๐่2’!zd๙™vE—๑,ะฃต(๊ำF๚ƒ๕Lพ~อœlW]ง›8ŽฦI0[คฤ๐€่ะ‘:์ไ”xนmร…bMg๑G๏ลัy!ฃี^w‘4šฒพKฐVึๅ~มา#P๕Cw‚โ๎}*“,l.U?ŒdN ™ณ[MYeึฯฒ”•xฐ–––๚‡" Tuˆo …าำัศ›ฒ่†ŠกZU3ั€0““ซa”F#ฐฝ่NF0[ส8N)๑D2ฤ(จ{G๛2‰aร—‰ิC ๒Y*fํึN…5ข,™^Q/€}W[ร.WqZz~฿…$฿wเgŒf}JปAŽตZร0๘‡Up|Exเก(แฺทAภ็€รกิพโื&W_?jƒŽ.ญ†าiุ;๋่H0n้Ž^/ฦฯ0ฌ}ฟœ้ ฝN๊ W า=ŽฤF€!๕โแƒLำัฃi'‡ธๆQc6ฌ›ๆภภ—ํบ(uถL‡g๑๔เูฅ5[:ษัhำA4ฬฅ้ตPi'Gm้< ช›฿เp1gCvƒๆชL›๔฿ม"ฺฑกฬฺ„มมgัฅ tU พ3‚uโ’.๛บu`รTt >‚๑์›สS์ง๏kI6ป้Gึ(ฦŠ'ะY˜งšEgื/ง๖ญีฆ‰M{`๒๓๛•ฆฎš}ปsŽโ1=่cำรQOtศQู;)#มฒ๑ ๑กIเฎo'mน0วอพfxxฒ๑ช Sฒาชง_๑ใ7W6ภปu{Zตฤ!‰ -€ปซ"ฌb๘๐n๘ขหเ8๑๚$EDNต„>„ฏวหใ~ส<๊ž{ i•่B<ถ=ๅ< :€W~%^๚}z ใฯอ•ีr๊ใˆ็ช๕ปร ว฿dV„๗n,ำqปรฉฟ˜}‡จ_:วฃ์`าUฺ[V VŸ‡ :‚wั$8๎6ฐDยa๊9ฮq๔ ี๕่XชVถฒค3D๒๐#hŽดŠ™Pป—MซTจโแ่ตฐตวŽเวช! d ฟ “ผ.ฦุษ"๛=๔+LฌCำL'Axช๚ Tน๗ล5z^\าคrํf# ’ซ\Zุๆ QzitP–*|ก€^&๎ุธDงคซข "- ˆd,z๕<`ูiGฏ]ุVพIqœฉ์ปŸˆ<ห8KV[‘›g™bีใ–cฒŸูซytoบ[๙ฮย CQ>œB ่Qs‘J8)ณ…ฅ†ภH55|”้TQAใyU&์%ยมแxลmv]ศฅภึฒจ๗–•Fด๚jŠ ย้@๓Hny‚tXi”–~_จศd๋ˆผุผึŽ#d๓rNž+ฺGxk4๖ณตผ๏? {ฝK?ๆ1า +"ฯjZตห 7MP๑“7ึ$GŸT 5“ภฃ0๑@่๚gR8Z๑เˆนGNlห5F† ฐดำ hฏjญ…•[.์จ^iร‘.ŽšณnฏK๏xh“y&้u‡8RยakGๆqฺZ‰B…;+M่ษ้9eœค6้lหN˜ิSuื‰pทฏsyd`ฐAฺตˆF๘“fเxD?jGŒ7Ÿ 6œ๋1 1k™xฒE…{<Ž[šคฑ9ฤ4•ั๗”อ๋ฒn—ัk7๐๖)’ผแ3T)#tœ(ฉณ›G๓าLR1Tž(ฆ๔๚mDํ-•๗T_ƒM]žŽะขดB€—+๖^๊&zฮ4FผR๛M$.;้ดQ?+>ึ28Œปก&‚ๅ๐œซๅะeรQ๔%ถqqฑj ฮ๓บg๐้ญq=‹'>ฏดuS์จิม-ม‘tKงิ>ศะกญƒ-‰AhBˆ/หJ;PึSEี,๛ฉธ€้๔Vูฌ™aษ3 _-^BJ)๗MใฒyF|WB6หชDDๅE|›Ÿ(p๚4ยฃ!ฎฑ์งัเิJQ>•แ๘u„กำแฺ›>jูด=0–tQVuืป"พ[ฒ< Q]ขc“่YฯRแ†1u .QวMo—Eฺ๖฿าS๎_อ๕H-ซZ๔ZI@;‚ŒQ 4T@๓๚Jญ5ิoฬ9ฤ<Œhฏi˜๔Y0ณsV8œบ‘๕ฐqœงู‚mึF 6ท5n—•บฟuุMM&ซ‡:~a…^8ฑ๋ีส2–ฺp5บ•ฒ|h/ฺสŽ*ฟSj๎ 0ฝ%บ•ข>‹_+uYXIe>’ฃฐ  'qlผHdๆตฯt•ๅะ๊ม ๚ศดpๆ~[yP4d๙uฮสั3$ฌ‰>“3'mˆแ9๒ax<๑~ๅn๖[ฐ#|^๙?KT‹g;.คดwB่ข(e้‡>F•&’“A—ฒ]†}bAk2*ญย๎}œe+๛ฐ๕Qu”ฯ ƒWO0qฅ?ญ๑”๘YฯจอตฦurF7้43ษKบึชีฉPZ>DŸL~ถฃkฎณ`Hf›<]สJIี“b~p€ฑธaD„Nekส7วฬ\7๒ฬ‘U‹(นFJ’ห€dรs˜NH‚๐๘)Jิ ๊sšสชภ=“บ‰H*–ง๏ฮCŠDj”P๕I.ค5เุb"9i๎!@nํDU?ู€1ีๅ8™ถ œ^[nฑs1x!U๑_เวธJqAVฎNฟQ็'œ ๔$c!cฝ(ไฅ)ฯPRฦ๖u\L2j’ณฏฯน(ำนตnฺ้L๗–"t˜BG๏ม:szŸ-ƒยM9ญฒ~ษˆm@Yฐ7› ‰^Lๆq` ซƒ ๆ๚—b_)uซbdV.ŠฯวะHฤ#N๚jaดˆ%ฏFX–V6wา๙ฏ๔Lฎ%5‡ฌฃฃ‘ŸฎตBfT˜†€a‹์/ฑ}๋/ีงw๘ผ‹Lฎข]™“™|ดยE(˜ŸƒyัฝฐŒrขป=์‘ฺี‡*฿๗Fบ)้ฌฐ๊~•L(KY ัะ๒ไ[šp28U ๎>WญJ]ื(-*9nd##ตwšิCฐ‡24gหƒ)ไhŒBoOช!๊ƒพ๚5nํ ฎ ศT—ฺืCฺ!:2์šาe—Xž7qิf๐VmL๋ฅf;=p!sŽ@R๔ฆ &q=ฏึฅไi ^บไuสvJๆ_Ši2ธ)h๓nฏถ“€$ญ’บต๔จแตŽW‹*Aฑฐฌ—่“™{N•ํ๒์็น*OซฒT๒xSใุœจจ&ฺ๛ว๗!‡/ๆ!กด๖ŠPซW`Oˆ ›วญqF[{„›EัC8Qeˆuหฟี0B u5ฯ ห๕<โŠzPŒภฐ„ํaŽbjHY›•JšUoฑašะไงป๛มแั๑m๏x`€้T Gค๖žนRS^๛‘๚อยฒท’ZN๐ฌh–ศA๑( ฆง3_เg๏:ฌzณล๖ู›ัฯ{aึi๓คr2ไwๅŽxšฯOUYB<~รq“!ๆ=7# 4>๑ฝœŸจ#fm ฯ6บ–้hข,ˆQ:๖ูlm2›Ž:,‹ๅAeŒ\fฺ๋Žจ Šยy}ฐัœ,สKV-Gชฟ…ฯษ๒<เH“ึuย๑ณ z ใhPศUVะ฿ื๒%“J…๖ษˆO O$ Z๖…“b˜w†iฉM้pธๆ/ส๑่S)ไ'๑H$t6ฐbu8hi๖ฮpใจZๅโRV"ง๕rศH๔f9นะP ~่ Cฌ3อvท}หฉ˜&!fไTฉvAiEoฺ๊ข^ฟ๛(Wฌ`ฏ5JA†ล*วๆญ'ู;ฌcฎJ/ุ๊lญ ไ์]๒‰๔>ุCฯ‘24๒าžU๓ธ†‡yšฒ(จx์ด€Fค –‡๓ง{.fj…ญชฌ!ถœ&ๅ๛ศึ*‹}ฬฟ็ห–รbRไ๖~jo…ำ_ถR%ฃร+uุ ๑+๚•ฉBฮj๓ ฒ}ถญ*€๘r–2™ภชฉั่UPน—ำV~ขZฆ–6๚ŒไB฿ht้˜X“{Gถํ‹?๙›Xภ"Ÿ๛Y:aN7%๋\[0 ใ wฆUz^&ฏmหMธ๖‡ืฏอญๆdTตA(•ฯตŠ๋‚ฐ *Œห–ฺ๎e6ฃš|–ษา๔ชaiงu๊nsจ9ฮšดhเPั๐ใ0MฅK$ผร2ƒ!์๕ๅ'c{FึM]$Fั๗ ศ(Iฬbีฺ๔ฝคโใŒ๋ถ›šฑbทC#3vธปaป๛ฦ๑แ๑žจa:๙๊ิ6 1๒Gำ4#X=j๔*ษฺ‡๖‰ว๐1UhJE[ ฉ„l๑์x‰P*Xาฦ~๎8ตs@ƒพฐ^YTฯŽ6๏์ ท๖ษ™€Ÿ!๚ ิxฃ๑9?๖พ9฿6Yf๊๗œขฦงgำ+Xฉ๏๛NฆฟhZ๛เดม๛š’พ ๙๘Sฆ4yA{๑FคAƒ,š$m ืบ&‡-l’[Œน๓ธึ'q…ล–G<0ถษ“ลษ@žDป วxŒฬใครฅ๛&t*ycSฮ๒>a4งjlต/984(ญ‚I๔ว# ฃ๘Nฃ้L๛"ฉ฿“'-yฎู#›.็'“เp€ฤ็*จ –h~;#พ๚UOฅโฮ9[/7เkฑ๕aฎไJ'๏|h &b„พฌ‰‘สษ๋–ฅvHq๏ษฝฒน5ทฮ\Qษ นฺ!‘ล๓ฦ8Z‹pœƒBk‹Kฏ็g}ภ฿๊tูbใ6ฺ่nฉถ1yล0!ธ2n6ืpดมนน0๖ลฺ/_$"Iยmูู%qฟแEฎั+ƒ–R๔šว%ฬ =ณลA{foE—-ํ~eงM*๙uamแว๘ย0าุำm=Tูฯ๎`C๏O๎6โS3ู)๑D8m0ดฃmtดว>Oป้Ku`y,hK%!%ศ_8˜ธt`ค๋™า๔I’ฅทล‚ถbปJมgš[‚  Yก:ษK"{มจJinNPT1š%้w(๖)&‰ปC8E"*yuDพ็X$4u—๖).™ฺ๛AœY๋#Fด™แ๗ œ_า Mฌmุ!พ'ๆ฿๚eTท#’}อ๕ซo2มวทX!„LถA้nฟ๘็ศาฌํย๏’Y~Aามฉฦ2ˆะŠฤืO…}กdุลำP^ วS‰ะv/+Lฑ๓,BRฑห4j>าŠชyณEUห๊oašฤ4๗ผ๔ŒNุdฮศ`โ,ฒรr„ๆhŸNข_ส<bชŠ<_ฝฺต(pฆ#~BO๏ˆ๘ ๖ิคนข]ฉ’๙—ีฬ•7&ฯู๛ฑฃฑ๎ษkMช๓] 0ซ{/ะฟธbIฆŸF;wลuWRŒ4๓ผฑ6บ๘`{ใP_Ÿ&&ฟ&ู—โŽ4ŒใZG!มeษ‚V%า]+<ฮŽŠg„ษcI๊ƒ ์Œ7˜ใ`ถqืผ๑2๋ :…›AฆGm{nอน;์nจ„3=ส\'v1ีdQm?–3์1ฯ ˆw.Gฎ{ Fรˆ”Mš“๊๛ตภ;"วยฌ›ใำ6ำ)ส?ฺAร` rพๆึnูฉำค๚ิ:5ชฮv‘_ฬOIeK(ฎ_ฃKูงแ™dKรO“#zŒ้ฤขศฃฉ‡Cขรณษšใ'‰ŽฌM'œง>ฒ๐่jœฑง7น ห._ฌziทe›F$ๆผ๊ศอฅ]eJk(G1+Y†P\o;๐oแž+Y“ลK+YโฦZฝJํคฌb~สฆแyCำGWœx”3œุฑ<ไฐ๙๖วโ฿๑ฏไw/ๆ+โ>^|๙=ฝา%7ปŠ'n๓ใž๗บ๗O}โ]>แž๙฿๘ม‹฿sj{E]aธยuEไ ๊Šrจ)“ยf่ผJ๊†ฆnB€ฌœAอf7Ž0ˆ}ฤ[X  "นnฤZd$ฉ…ุ€Žํ{I(eSi–๓–” },yษ†›ำแแ๘ž'O@๙9๐ฒ_™zอu๘p€0‘xežสsูvห+'e‘จฤzกq๚W~ึ\FVฑ๛žUฤvˆ\อ๖าJ#ป tฺpdฆ9ฑ๔ุ a‰uWญ5คFฃW*ุL_:|—ฐ๖ฎ^/ชl<6ฝ!)ถ+๏”ใ*ษn„gศv…8สฏชŽƒ4v’0ผถuง'ยC/mk<ฎfฒู:iฝ‡ Sผ c\ว๐ฤ›๛ื“ฝ'WปSŠdžีŠดY… จ…ˆš…ฑœ8mดn' ']8.€ฝึ–yฺH@9/ไ”Rฒ›ดŠน"ญ9ใ5˜ CWภ›จ๊fx$ผทD๓๎w;บแB0rDขฌ@N๔‹วโซi๐-inไจ๑vีธX›ZgOS›œM‘6=ผ W KS587&๗๗ๆ‚นน •’qฆO‘ถนhW–ฮ2Žเ9ฎ๕Žษ๒๔jšแๅ}{Wp:h?l37๔I”ึน]ฦNย2Vˆc#ฑ+!bไy๏ร฿}|ิ:;ผ๒œb‹W‰ฯO๔ พS[ศ[+wI,6X๒ฟตส;]๗.ห_ิยาฯเˆร๛๒SรBŸฤ็ฟ๋ ดคfซ ^ฆท่{s‰ฑjl_฿ ญณูฬ=๏ป7*ท๐‹ถŒ]ฌšซ]พมEฬฑ>‹šผดฝภWฦkฉ์ืๅn“8ส๕๐>8฿‚3ห|๕ภโPึ้D“# เ”บใ“ฺOMุูา‹f’ำ (ฝpถชs‹๋s฿ญj™ษ"หž$@Q$z€ํฏฝ€œ–แ;8ญหv฿ร๓‡=์ู๊ษNฏษEกฃฑฐ9^สBษK๕=6 ม…0W6!d,Jฎj๛[5+ œฒ)ฉ h‰gcCฑ=&๑7”ัฺํฯ[ชn=ไnร.ืnLำ™y=vู_•H่•pr‹\๘‡ใฅd3‘ๆฝฐ.bJU๏ŽH๕ิปศ?–M& า˜๊! ฬ[iบ€ห‹Š‰Mฦa”Hg"DYฐ„๏ึHหˆ#บŒ๚โูึ๘{ฌ่g‘ุ้ชWมMขำD"Gg‘ $j์ฤ{๔Sซ๏ต„๙m˜kญ“^ป8้็)วB#Zkšศ1!™a—๕œLึ:ขนxCF๋“ึ ๔ฅ/™่rฟงข(ฏฏL•แS•†TZ3{ผ๐ycงด‘6~:>- aฤ่ใข?XพฤyJอฬ KฏQrง'์กนW๎ษๆาˆึ‚ห’—ีผฌ๎ึUb‰_ฅ"V-ถl๙…[ปy~๋+ 'ธH๔๐ œฬ* H 'ไ+คซํ+ vภNป#ึŽŒถึฌิๆาUECœ€J/มX๋ฏ*E๘ภหPบaฮUc4|I #:~Q>ญญง& Cฌฎ†P๔จœŸbวKWห4ไฦ†hB๘lซข9้B"…4tไฑ†&B˜Aƒช์ึฅพฑลู'›0ฮฏฯ โฏฺ|อตบพะ|}๋f฿’.์}ฮงbVA˜a™ฝ่Œ)JCย2–ฤB‹ณf_ฟฺ/ฤ8้€žRษ$›|Š!๔ฃbฉ๗์สพ๑ƒ=๖ฦ†Lๅs๙8U›ซพ žฌ-๚‹›Yฝฆ฿†ี9ๆา$ ฿\ŠKAึล,™HfแU๋Ÿbฟ๘์_็AฉŽQหิ”:JฉŽP‡ซรTWT ชUจ๚UUชRUฑj•*_ฅSe&|<๙กไ’’๋“๋’n$1Š๙ซebู|1-+1ก๚‹พปYC€๐ผ+ mhAP‡T็ฯ-I/=\๐…<1hศแ๔Ž˜‹ุ ภz”|ู่ง0&๛!Œ)|„ศ!NŽ$4ฅu›fะd!„“~3โอ™—eูŠkžึyxจ!่S ิน5๎jถ์ž $\ธไGu(๑„9ฑษซฎ&Ÿภ ๎ฬ›Qb2!J๊ Yึƒ‚95>ภลธh๙pjdฮmMeขฒC๋*Q็๋–พ&๗”ฉ๗GจSล7๏ชa;๘@‡’P*ž-ๆ6il)%^8"@ …–์ฐชฅชs็rMหFฬ๋ทเ7V-จฅ๚๚~ตฒp!ibT>ฆ+_ฉ‹ๆƒๆB—šrฉ๕๒xตŽ๙L ูงฯKMd6QMฮฆZKใzจu้ˆฬๅJ‹~ฑb}พญk–ล›—5ธ๏๔„x(าlณžชถcํษ๋SE“๖|?%+\ขผPศ?ว฿K๋cนŒเ>ฦม ้ฯ"ษ.mฦDzDษ€๕˜O‡ณ Ž<ษท˜ธี?=ฉ2>SฯฎUQฯ 5“/$ฉป3ณ kถuลmู้†ญฤแ๚•S2ˆR™ำ› ฺ่m๙†^Jรั˜๊S`–’ฉนDSmศK~#ƒ๔ฒegใง]dชJฉ Qเๆฮ‡Sbh•k ฌš&๎“:ฑ/ืฃb์ไำ.ไโsOœฟ[ › &*+,๐๒[Šคฃนน\ใ3‚ฅ`3 ไ#)VZ3ฺุRฒขน๘ฮ ƒัฤ l ํ;๚๘ีแ}๒ฃuฅฎๆา1:@ไา น„ิค‘6ส๐ะ๊ไH4๏ท‡*ฟWh๑&ิŒbP™ชv|ฺ9Lไe-8๋ฌD ๛Sžี๑ฺๆSžBgœm฿dxฤี“Sู๐$DTก>~7ฒโ๑+"ษ]ฌ ฤ rŒdฟPิฤุ฿MิŠร^ซ6ยžส$฿ย†ฅxข{qญฟ+‘K[vดKA† >ญoึ‡{Gj ;ต*mฏหU๋1Vลซjy ิYษท0Vุ๚•Qฦ™คiตfฬsDEม]D iมIจขญb†Cผ…2ล‡5–๓ฏIฃผ๏ขชๅ™žK๖„ๆ–Œx๔ณ-|)้†•ขแu๔ˆTีืg•D…;ฦสf=\{F–žสUฝ{’ƒ*N๎5~ถ_?ขธžM‹€rสz”ฎF:~”U GCR’• ฑn๐{tท[‹™ฐpญ•ึY็*w’k๏ t–จ๑!‘W o๒Z‘ฑๅSยoถYยW 6ไ;ฤ3]€๖6M“G`r~ฆJ|ฦ# ๊gะCwAm)ถkORฉ ข-Z ฒ^kcแ ข^xE=ูณP ๆต~ug\)Wฅ_อ^ƒ]๙Y0Žล<มท7‚Šฃ๒Z Kถะƒๆ`gK6ฆ้)Hm,มuƒZมP ™s๐Ef|ๆ~yv‰ง๕:ทŸฏ O o฿ล๓|93–rฃdy+ว6}ˆ^อ(;ฑ‡vวพG=‚‚[< Ž‚Žพ‘ั๎ุมม์นyoV1Aดˆ"0ี[ >Ftัๆไ0'ยsชข/ฺq%ƒึ8งญaeZ๋ัst^Q2•ดŒ‰ฆวๆ<8p>`!I"<–‰dB};->๔ ฐ#ฝa“E฿‰^€…ฬิ๓ึˆ›ดTล:โ:moุป๑3ข็ูVษI&เ›#ห—ขŒฦ$Fๆ„ uธJกิจะ‘o ไvneทu*๛รC^่ฤฌ2bGฅK๔˜D|ร‡ฆVlร๊,ณ’ส1ฯบโฅWๅเ•ห(ั†ไ:ฐขhฉคใJ๑‡็Yฯ’ฅสะRดž-t^๛h—€ ศwขฏไq/ๅ—ไมกชฦ€๛fq)>กปBดbPC๗ัNk‡L๑—.่4sb|ฐท™าBงฤะฆ›ฬ—ใ0M1oัqžฺx Tž๎ษฆI&Uฑิ-น๘:‘P|ดขc[74-นีปXc!^€Aฬ๐'%ูๆ{W „ฺอ!฿๙น฿k%่‚ูrฟ#Ÿูz|่r !9ัn6}ัแad-๐฿๗๒ƒžส/?9+nhK‘/œึ?ภ-V>๕—ธ8Wภ’œS–แNบj|๗Mข)•้ž็‹9'็‰%WŒซ„‡๑ ”ฏซใ iV่„V`K๑ำ8g $-ูj"|ru œF+œรNำฉŸ๙Ppปf7~r*ยต=K:วึ‘a๊ฆU daˆœ† ๖}Nศ8jdg@฿ ‘ฯตฃลส_GyXป.ธ*ฝ*ส;‚หก ญฝW‡ิฎแบ@UงIŠาH+€นAบฉPU+dพ}Cš3@๓3uยผ@พPฝ๗PิlใGฑํX|ฎืDh๗1ตSg–ณงฦ฿B๖uั‚™CS'‡^L”&2\_สr`ไ^•–๔มธด…„*ˆ๔DmcšU๊๙เhผ๕%p(W—ถ|?0uSฅEา้9 ฌ? ๅŠฉใูฎ้NฅลศVฮ=นˆงR‡3ตpVๆทข0TฃPถcsมพeฯท•ฎผ`๊ว@๔์t๓[H0ภฌ‰บำD)๊9›ีUฤฤฬMsWW•<^&˜Ÿ๋อฒaๅ๙eฎUน’#šAยถู\c๛ทOฆโn; แCuDํ•ฐA€ส ภฤ๊าn]ซG\=Ž๒Žใ9–/žีตb-ฟ\8 a"ว€1jb––*’+yลUฒชr%#Jต๚9€9 †จ‚9!แ}ฬ|KYาๆฬW๛ศuhฺœ!=Y‚>$4‹… คู้Žบp]sฏ%"ฑga% SK!6dA๋๒a#‹bญDgมฮพ่ัsศfใะvนh ซtC้จƒ$ษ5ฝ ฐ๖O -0ฎ]‹Œฝb๔›˜วs6ƒิ๚๖“~xwsFศึ๊฿ฺถฟตุV๘Uyไ I๑xxจ๙ืTP2I}a ƒF‘eใfฮ2"Ad=๛ถฬ่V:EŒ๋5ST3ชA…ข๏}เšท‰๋{ชH–‡ฉLธ@9ำ&ๅดงUZ95ˆหAต42ฏ แux‘i'$ฒ…I1KAน%นทŽ์k๏D๔ขž‡ตoิ๑ฏ๚ ฆ๚p•พ ใL@)†ฑvF๏ร‚F่ขฒ*Bบ๊›๚Bสีท{๗R=ศ๏6ไ,ษษJึว Tิ,G`๔/%ฉ42ฌZ *ฬ&(€tˆy์40nธวืฉ“ฉ๙ฃภHˆr3ว gj›[Žช6@้สพ8$ภ™:ฎไQ๚YJ9่E†ึฺkIHo– ๖บ7—~0ล;”,F๗€ ‡ฬjณขW_์Mข Lุ่]่…“E Vเ๋ฃ๐ฺ๙ฑBญˆ์jFผV U˜fชB(6ไbjฒHƒbขsณๆt*ฃo)…Tจฤตขพ๚ฎธฟmK๓ษูH๘Jๆ*0๊AsฬสY\/8‡r‹๛ธ 3š-^ฐA}฿Gวy๚™mชr๘T<{F=ฯพูs92๔”ๅ™ืDกๅ 6…ีิ^์%0cขสw๐–hˆF ‡a์กmIลN`:jrdเฒโu0Sฝ+๑ตd1Jฯ„๓๚l‹^ˆุsฟํw/‚ึU4œTน2†ี4hฦไAQ์๋่6vX1ฆb0*ทa”ฒ˜ji๙ ๘็-ZPdLิฦดั5[Lฃ๚๓9 (t@•1 น rฬะœR&ฺฐข๔"Š:'ว@šิ๒cNึ$„์wž่D70๘U฿,…"}[ณฅ{'z—(ฅ๊iยCg07๑ล(/yฐศ๖๔|FฏZฉG๎>X๒qtะ‡ฮํฅ๘๑N”ติEQกQ  ๗ตฎr%(†พป๙3ทผb>?vX2ฟgXต‘๚œ๗";็Wิ]—˜}ฆD๛5จTๅ•–Š๙ำ3้บุ๑ฏนCX@‚ต„่=0 Pภรช๘Z*†๑.ะ5แ{๙yqcUท่ฒ๑ L? ั๒๓๛O‡ut”]o฿OW๐ธž@ืๅp4ฮf+^ โ5jQtฬ๐^ฆ๖กsŒFซ;๕:ุ>„วบร"๑&ฒ็vp~ MำKxฐeCn>ฌข€ 4ๆ่€x ]w”ฮšrฏ…ฉห E6|~–ฎœฝ sี๗ธ‡_‚ คๅจ:lœ้็(† ๗|ษ9๕นดฅUTo<฿MB8อnV•ณNร1นDคŠ’r+ฒ#ฮจrล•%ouyV>ฉ๊ฅœSy›30๎ษa^ƒx=ฎคอ”ฝD|ฎ| ๖&+TK.!œ])oึ $ืžke&‘ Iฐข98œ0ถ\gp‚hrท1PP[HเๅpOŽฐ‘*า™rBษขa$)อG Oบ s!ž3`้ก‰$ฝฏ<บk12!7ึโgwู=}Meว21ีHฒ!ณqข[๑ฌตฺŠy๙ภ็lท๒อyŠS๔ŸแŸฏ/ภ—pl?ช๎„ `ศ^ธOศ89ิฺ|›ัศ$Šง๊€ฏ‹๓0 —$t2ณKฆoE˜ฬ ลˆ@‚ŸๆIๆภq๚x –…@ช–/˜vรน``ก^ข-ห๔pZm pฦv ฆ@7๗HHๅl€nhฏeฅ๕7]%,๐ ›ฑn"ฃ™'๖๗รd;0ํq฿๕ู„1ฺ#e ‚ฦู} uิ๎ฤ%4ั„x๘๏cK๕Z=งศฏฯ2์+ๆZQ๒“q ๑VgYซะDฯ3๔ํ`ฌš9cฑ Oผ$๏qฌŸXูิศึ; –{1—vพถฟ4ำ๛‡zMR!ฤฌu hG!า๑NแbญธึOฦ๛.2ะ{ม"Jโ'ส ฤŠู U€คsๆ |H9๖(Ÿ–‚ Œƒ่•Œป†ท)Ÿ†๑kฺ+เIค^ี:ช๖๒ว๑กงฑ-ำญ8๑ึeLz€฿๊Jสํ:0>ศ–ๅ’”ิkb๙€๚NžฟA์Y๘@gIๅํMๅฦ^Hฒpz4\๙ศ LPAใฆหยเB5;ีัŒ)ึ@qŽพเ ฒ ม'4ั—ศiงv)@M=M%AšV9P'\ลPฟ.”>๓!†[ุส!uคถิ=ฤGถK_nจoุญฬ‹N+่ฟณ1ผ\บคa‡่? d๘wุยLวMโฯž[๒q/YศA๚rs๛fแสLก-้N€Dแ›ะBCแ=,U)ญ฿ะ็^ีนWr:kงณr‡๛+UhID Yฦ๔ัR >Z/ฦ7ข ษฮทตJESํ๕าเฅ๕_8Pว็x‚Mpฐ0ยไy9'+‡๓!=๕‡ิ„ป`ต$ผ m’ืl,฿1ใฐฐ(z„.ผ&ม‰‘๛GสaLแฒkW2 Nภš%ช/ม๋_U๋ŠชpGใyLDufqก-ik™๚ุ ฯ[ถ(N๗–hดE–"_โฬี ึHโต$ฺ}(‰:wx่j' ซร‚อXื".2šทฎ\†…ธะ‚IB+ต๐NาFลพuึ˜Vเe'wฝฏ`~rฌ„wXฒb7๚o ํดm้€๘^ก?ๅ็W‰r฿Fๆ@UF~Oังแœ_„_น'P๐๐C†ซา๛ๆป€L|“์ู™‚›W+ฮ’gฑ,%&d2t9๋lษฒVjaiN๓ฒO฿๕'วCฦบก๓ใGˆ๛็vธyด>c๋p1ำd\3ฎ’ตpฯ า๗ฯ1ฌWฅEณˆผ๙ไ„์[LƒๅTภ•–‘ญ—lท“Ÿ๔้„@<1uํyj่เŒฌž@4K aฌ j๚?๘= ’hžต๘ีิyDmะแkž!.ุŠL“๐yํo’ิล ฉ‹ู’1Hๆเศ๋ส’Yหšฺƒ "ะzƒ0้ขe\fŸ่f1 ŸปqO%/™ŠฒV;N Z  ญš^L2ไŠผ<ฯ๖T‡ณ|m<ผ 'ศ’น™ตฆฝwej„เนWˆU3qC2ิ ‚๕9”๋ๆเ=8e๓DJ€ฆVฃซฦน…2dUˆฏ*S_gจ–Wfฝ ิใ.’G็lฌq~NT–”๔šุ๋"ำ–1!:น#`‹d&zแตYฃGB้๏w5ิื‰'VีM0ฒค๔•ขœ(๚he h'สM#‘๑ำ‡jง๛^h กำ* ฝ$ฯ9šPฉ5ฑ™hmเอC๑jvKถˆ๘ถHRฮะ๚ๆ}๊( š$ยBคฌuT[~‚‡Cชอ–r๔›ช;^L่T'šผž—•u *ขฮ๏พHบ1ฒG‰๊ศ.Pฅ2ยูบk=Knๆrฅุrะ๚น๊ฉ*6กในลแZhเzŸๅ ๆ:‘๓ใ3ตoŸ๑ทG๘•K;”]3ี“\ฟ๕าx๒เ1#:u?ฃ:“คฺ5ํ„“‡่แjฉ}’ซ?•ก^ฺํ๛iํXภๅ*๛ฝฎ ด@ัื๛สทขส๓ฆkyžLฅƒ3Lฒ3ฯJ๒ํ(อwมv“าย # ‹ปธ฿ลฤฉ ฎ‰g!<วจ ฑ#Uฎไ ็ฬ(=Lศ๗QหpฐZhGๆ=bชธIำ้IIฐฬ> ะ›ฤer|7าฺ๘๎'Q…t้ผ ถmฆญญˆ4<œ|Š8eๆH^่p๚OjเO้=—ฤ๘$โ€%dqษ’T8‹โz็ญฦ๗แq2+ั‰ง‘ืาZ!ฮ1โะP๙v๒คS ;ฑJโAิ”้&cญ.9rด้ใQ’ซ D"Gค๚Qตฉ„šyฟ9ส–$ง๙ล!๎˜๓v$ญ"๚‚`ภ9 ํ^ช;VบM`ง9Lกไ™0่K1ฬํ“^-ซ,Šำcค]'ั˜ฏถบ฿hธ๓์ณ/=… ”M (‚Q™˜๎ฐะ2iท๖ุ0อ›) Tฤ—•ศล#๔๓ฦIF๙O“šp,Iy;L iŸูž้kใ‰ฮฺนา—จฌ(๗ ต1ต=qฅง’ธ:r๘๐ิv1`ภX–k๒Žลiฎ—เ4™>ถŒฒ— *›ู3ปb_ะ‹ฎ“ถqร%ล/ฝัท้+โ1๓เฏ[‚V๏/ มSO\แ๚๙เ$:Hีn+จ8•ฏDค!ฐf}mš%"ย„“–5แ7/iŸm?Œqyปฅ1ง“โณ็ญโ9l๕ฺ?ีm‘ู0(ีฯ&› 8fŠFรพก—'๒#ผ=*๋นฺ‡็œ–ณษพ!๘y๘<ฐย9ฌY†U ต ะZ6กาJ@Hgฝฎh์RVฆ|H฿ ๐BูLC‚Gh&ี|/e5 ’D‘eถษB,&wฟก™]บฬ๊ฌ ฿jฉ}ข’ด!ฬ* ๎lDไํฆ0Lศ1‚”&7๔ jGiฎ<,๏)q"4?แ ƒ˜I6I-R‘_ƒ@‡ฺํEซฒtV@็E2(S้I@ณWฒsดŠขคŒฯjฤ็P-ˆ@ไS๓hm๎d)&{UOgึ’ฉ๔m๚t!gตษฌ!o๔g฿j|^‹้Z‹‘๕ชซ‰v]Œy‚ฆcF้โ๐—ฆ๋8CŸ„^ภŽX็"S+ฃ‘oฏA-y›hล&ธEฑ)๎ปw|trjŒ>๕ไญ7ฯ?qKึ^fเ‡?6ล฿|๋โSท้{g&ฦฆ&ว$T /Šฏ=์k•+4ฯณ๋t\l ฯๆุX7ึซFŒ ตH ต4€z๓ปงNีI่ใ๐“š H๘ฐQ๛ฟะฑŸ “'่ใฒ[ฅะ(๑ฐฉQ‰ o๛aขฏU๎Šm9…ัSU9ณฎ_u‹gHเE??ฝmœจmŒ™“๒D้€š QษMC€‰ฎ)HDY]ฺ 4ถ/อฒOczํ"3๛๏X8ži๑.ๆ#ฦ–๘.‡0(Mใ6ทmG๐A0อ๕„|”฿#vŸmฏโs๊๑dุาZW์นน8ษ˜› &fะQaA;ชฺ1ศMmpV|,๊นŠศtธีn3ป%ž"wa>„!T9Lvฝ๑Ž๘Ž0{฿žฅฯด4ณถฌDญRh†…:rzศŽ]"มตpถ/ณดฟสถ๓ด“ZCp5“ฬ!ั๋cซ"ืด์ด~ฟอบAEŸq ษฅ?mภจฒCุ… 7เ๐Š3]Ls'Œฅ<๏ฃบซศึฝ‚‚ๅง๔ 2? l๗ค`จฟฃŒ"๐‡๐f๘ฆ่_ƒRFษ ;“UrM‘,ิมck1hโlyvฮaศซ์;ฑKKฌcLฃ‚K-๔ณ˜K พ๗x(:่ฑ‡„˜้cฒ฿๕มทมC‡อฮฺ@v—B€wuฉ+ฐใฤ‚œ์‘ูผค^~'w$์ศtํSj[VNIrยขผ'เ-๚ไŒฃ–7ฌ)ฤM_ธi็ฏ๊+d4ศx™‚"&/oีตผ]ฅน “๚$‚ผ๗›ตๅ่จ๗ fƒ๘ยMคI">ิRO‘ง`‰จf {คฯมชร฿.Wฅแ็ก‡j ภ.\d`๔ะ๙ำ๐C—๐v;์ิ์X๋"ตGŠŽ.sปRv๒(๗พทH่>uMขง๕ทG1[ร๒ๆตุS&2ิcqฤุ,™aNเmเ‘BO>ช‘}ฯ จูHf„pฆYซr}&jธaฎู +rเ@๛>’‹=ค/Tƒ ยสผ~ฎืS“ฅvA฿๓Kพu|๕)•‚a  นแ ๘้ƒmwศpยFqฏทvิ‡T;แล–C่U/78P‹>่76Kง๙าาฅ”งŽ๓า{_|aฦ8ร{/‡ฆืœp.ก$แขฯ|่fลืฑงลJดน‘4แšs4.ัP# d;s็Œลv๘2K3ฬห"ณš[€ซy4แ1”cู'6ฌg;™j&(์e<{L„‘ฺ{!4ดp๖{P‹(oŽeลฉp„ผยfˆฒ์“˜๕'ผ฿qื‚นฑ U%สŠ7%ื๒Cิฐ–ไซ€v/ิ๚wด@ˆtŽ\&ฮxjƒ๋iฒผ๎$šตƒš6uฺ4ล7qqฐ|ื d•ศ N้หš6๛:pหOZ,™โ‘b6ฎชขYซข,gส‚ฆ0งฐ็ZซyFžำงC5aˆธBLปอz-”“'ะ๕ฬ๎ำ$๔“@v7{fg&ง–ccนอ…ƒฃฝCศํ\—]$_~yO—V[พiะอoน6€ AเTภf จA !sผ!MณUฟOjฟ^ทVVง้กโค`บd%ŽU๚J ำ@อŸ๏”y_‰wSN˜ !สะOถj`y&œƒไ7nฺGึด:'ณ฿้Kใ'‚ต$๋\rัศrSk7|PวJ$ฬ$๒อ่˜6ฐ>่s.A[ธ ๑๓LDฤๅ€sx๒ั๓ศxq™iฺ}ญ๗E‡r ๓>p๏๐งฟ>xง๕ี>ฝฒu™a~ู‚๋ัทH๊’ำefนกˆa3)๏์Œ8น…W๖,G๎ +Fฏ<7๛D,–‰š้ฝ)ธทcjdิ8fหอ๏ช๗tๆเƒkชษท6X}Dv—šจๅ‹5ณฏwi็็นฏ kภ’~t#ฟจFž?D๏ึศ฿ํU”ฎทl๚.)แยัม<ชอš iฉ$ญฐ2ŠrษขJิวgy๒œŸ๊y™บ_`“;6e 6 c-๑–๐๛/์ๆO๓ฤำฎษLwฏ-ฟ„์ๆฯe์Aฺ^‚xรU‚๔ๅcm7n›ฝล๎หษTš+kไภTป0)uˆa1๊ิป๊PsJฦฅ๖ท˜ฎ.&-ฒ๏wษุ>i๎Dัดษ’VG>#ซ์Vh~>แ?[”ฟะ'๊‹หuX b’ตฌๆ”ๅ#Fฏ ฎทฬw0—by๕้lMฌ่Aไ^^K๓๎:.ึป(gu“xส คt•x.ษิ3ฮN๚กสKw““š๑ผ็ฏ|gzhF๋‡ฐJ%|v•๙ ๓่Wษx๐ญผะีŽXูแ6™–“ooฑm[‚ุœุŸšะ๙nฟ&ะMฝฅฃ€Lhืฬท้ก}๐ๅ7ัดaUgฝW%กek›r”ฎ’ฯ5’฿ๆ/ภa#๋#ฑ…mJใŽ†2MปJา=ยšE}.ํฏ_ฯ”ผwŒLฃŠ>บ8หM•ฝ‚ฑไ9…FW“@*ค๑ f#หฌsๅฅฮK ฟจ†?พ๐M8…]@Xโไทว…7…{@t!-ฆ‰ฎX๖ แรZGมmษธฤtงัข%*’*x=`ชŽX๖‚Y๋I6ฐWC‘๑Lฤีณ<+ฅPt=ซ7~ƒ~ vJ?๙เํธ'N™ฏi…๐ช์Qzู/x=ญ‡A=QŒzศM๐๖ท๙ฟฎ”๐ทŒd"i3หG,=็[ p…*5ขxถๆ6›Ž$bf]7๏์#.‚Jx พ=rMm.๎แาPy‚P'ขฬพF_"2,๙๗Dๆ›ฺน‚ฎ‘!๊๕ฦˆšฎBV]ฤม ”UฅLŠก:‡บฉ๑ณU&Qb„{%J๊-jnสMฦA่q`ร๗ฺล}<ๅฮผ ญฎ†KษyY;|๙ญGD8*ฟ้ฎ‡ๆX้GIจO }ฝ\ตpิิซรฏD>"ฆ]งnฺ“|ฐTเลHกLบ๑{[„ˆํนณกฃ’ยบหw0๏\Uตฆ๋7™T+สจKGฯอž8 รUณ๋a`้๋@ุ’วล P x(ง:\ฯ๘ยmห๒ ๔ƒ*, ’’/ผำ}ฯ๋Š๑8biDล› Tฝ#-bfŒ~2 _’ ฬx •Pฃzs;๗—RลณYา1Ÿ*ม@GSๆs7พตฟฯbฤƒžััE๗รน4Wจ:๊t&_R฿๛ช๗5๛ ศ6‚ ึดำ~ฆ๎rYh/6šŠ๎bJSฉ-ั็†Hณน “ฐ "ล๖ฆ&zŒจ$k หvTqƒ5 hq๐ผX}Kdช˜/ฎ†•eถหํฑXUqยF{ๅlรข‚™Xb0pUฉ>ƒึ… •ฝฺm”ม$ฒ^ ย„‘‹ค’KA\MใNj5๊yิNU้rLS์+๐Beษ ๊U™n๙|w>lลhSะoฮโmcฒื ีทG่Eใๅ‰/Oมป o /[GOว฿ไ—ุ{‹oํœ_โ ณสื ๐ส† \4ไ|@5๕ว๋ล#’pวจBืรๆชษ่Pช‘W&4Oทํ!ช๚ซฤญVaฐ.o๓ƒํำ@ลGyขษu#ธภŒ-pfลsWf.ุheขฦศง๓คqธค†ฆ]Bฬjp จ •D=(ภ˜บQคช ึLŒK-ลXmv๑ož{4Ip๔๑NZžUฃนVค6aจ}B•ส2‚‘๚ขNึๅ-xhu+*=ฉiสacg :™/1R›๊ะ%ว๔ด‡iทYs–Eณ‚-ฑj฿… ๏šฝ.จญํ€,จปิKžซึ’ฟ_๖H6Zd๚|X้ปw4—๛B‹Šฦฟd˜ฤ{9ป๔& งYนcC๐ฺ/˜โ2ภIE/e^ุŽ๏>ƒฝmf๒ปB<ŒเShš‚๖ไŠr$น™vอไใ}อ๚ดg้ฉw๛P}b]ฟ๖zฉW๏$žส๗คฅ2Y'––Tต[=ๅvOปยป5ฆ\ๆ*wŠ=์ช5Wจ‘ซRั๏ชจ—Wฤ˜~eUฟC<ฆชZ\yะมพๆตไไั*šxชช)}!n$ใrŠ N๕ว—bœ ’ิิ" (f&)B7ีูCอๆ[ˆmŽ”ถh[•pJ‹iจ]h\บษืŒ๙Aฅุฤ>œ—ƒ3ณึเ aโ๙๛ จ™9ญ์ิฤ ๛ษ฿B%๛Mขp†ข5ธผตตt^l๗?h๐lฬำ๖ษ/$ži่\'HIึ+W.๊ืึำ'า~ Yฏ†ำ~ ๕Wn๘ร< เ,0N๑€d,>€ฝ๐R9พ‡p>1– ำฃส?ddC–ลว‰qi ยะ๘jช๐†>*LJร๖ภ•,›ๆฐ#„v €5 ิ€† ี๊ฎ^โK€j๔๛๛Žg๊พ –ด„”ั–•iษู{H,?Tr„ฆ้‘Mๆ"ถ†แัB%:๕็=HEัจ๑yฬ+ŸฅH๑ผ|Iฅh‡๑AR_TŠฤ2 จ'D—@๖ษรคL๐‚๐œซ•ภ}=ภAVSฑ†}๓บžื๐ท๐sŠยeWu/•่0์M)ฐ„5qA7ผ-|I$ห?!Ž›แ็อyฃ ๎ิ๒–M:Qdต>•Uhr%gปํ>6—ฝข”;Ÿ;ร๛™Oฮ‚.eKyำ˜ดค6]& Q}PVณํ ๗ฺVpa‹Yžn{ข็งg_t^c†b๊ปXส{๐อ ณกFแัeฦgdุล0๒บaE†ูฟ4 ฅvœพ๒ร๔ Aซ+YŒGšOฎตŒ๗ฏบฌO? ฬwฎP•w3เ!<%N+˜zภ#"ีSSๅZฐมs ๘ุ9€˜ ศ•4ธฬ่>ึ[฿ฦ™ั ž“กญด๎ํj@=Xt|ฆห’/๔:ฝผmส.๔๒Fp‚ฝqmแฃ›ไ0ซZจ%r•าว‚Tฌ>hํwbโ†wจ–›J ํ@ม_77”m*ทŒ๏แv—๚0ฤtฎž๘N-Œ3ฬ*Hœ™dฒZฤjaŠmีฒ#ร> qSan|ฦ^แd†ิœ…už๏๋mณ, €uˆf๎ˆๅ…ˆฮ]Q&L:V<ืI|WSŽnดฺoh/Y€V!`ิšปฐyพT๚F~ฉ:5ๅ0ณ฿;พ˜ข]m+v—ญcฺh;”iฏตึ๕๔ทgพqพ+Sํญ้Sมไ‡ฃ}ณซ}9฿๗,โ ฅ[าบ9คนX_CจN'๕๋l‰+็+๖=$nf&aน`๛GQaฦO฿๒์ำท=หทฤฯ็าR+G)ฅyeJณDŸ๖ ‰bฅcชbt‡œ<#นฟ๐ญœปฬ>น+{j“kตŒN+มPชตŽ4oฉx‰Š•™ ‡฿๕่แpๆ๒ใแˆtึ๔—n„$˜ูŒˆgAJ›็๙๚ภbs9๙๘งV๖ฤ~vHฌ6’aย'5สัธืŒมะฉaเŽุจำฑƒ:-=Ÿ๏aงhr†ิ6๐ผณ{wSyแ\?ภฺ•ฺ๛รห๗šนaO?’์s๕์jฆ์„ŠdŸ Wrณขโช?#žฝ<K้'Vqr_คำ-+ถญฌ‹ชง•ผ™ล8š๓Pš#ผๅฏ|fk},ซ฿ไ&ชกึึSqๅ_iธฒี’๘ฏxlKส๖๙„่ฯ ํ! Nฉฬ ๛˜uR๓\o ฿™=ะ^๚ั^๑}q—ๅfžPYฌ‚RwฒvไฉนฐvPm๖fง๚˜ม่ึ๖๛!ั]€R]B€๚B็ฟ้›ีŽ‡U ‚ชFg&ั-๙[=ทCัแ–พGw ้žััธ_รง1น+ง5ฐ`ไ`/ฃทา ƒ[ฦƒ$7๚ืฆœ&ึฃฦฆ^ฌSยถ%^_ฯว<งo•ไะๆw OดBะธnฆ๓˜r๊์’'ฅš,ุ~~tj23[ศgณ๙"…ธำ@:ขรโ I|BถDฟ2xM€Vฐ‘D๏6lท&š_‰bœหิKศDz;ฝtฆี™(ิ&ณ @งcnœปQฬ๗—ล๗ผ9ซณuฃCึlsฟDฃ๎•oต พ๒4@นืC!‡$ฬ฿Oศ5y้[๛{฿ Gœ ั7พ†‹๏[ฺvzีฑJnอฅฝ๘าฦ฿ีฑฅ๎ฅฮ!E>c;h)˜Uตฎฤ&\;๙š wูLm‘ ƒQฎศk“™t6ŸๅฃนIœdg2ณ™UฏฮKVS…ภัฏ๊3sภี(๋ฯxrp๋‡ณ็F๖e)ฃgง๙๎งฐ9Sสg์ฃคW ฮmรฌใซˆแI(uโqฎว[Xz_eุผสื์xฝ›4m ล)=O!mKา ‰jฦ๖<žW>๓Y0็๊J;ลc 6S็กnอ๒:ืhเคีฮ–6œ2P“อ…ฤŠ˜‹D‹te๏*กษศ‚แ๊t=1๚ึ๔˜ู๓ŸฤิฤMWโkษภ{ั๚‘‹๗/rmํ’$ีvw).‘ …m”ทM^]ก”5Gg—๕Mตฌjหบฺ@๏๛ฮ}Wถ?œศš"cdXฺ—฿œ๔แz: HะŒp™๑ฅ๖_^†{zZ•พ%.F๙าํ‡8-›u—์ขเkฬ0ˆะ๋ฎ๎ฉ_ˆbไ๋รขฃ๒๓ฃขรึ๕๓//ฒิธf~ึฒูํWศถึด†Zฐ?ืตyi?้T>&า๕ง?฿ฃ๚Žฺี'Nx๗์ี8๎วVื45kั5ูeนลฌ P+$ณF2/แ@cา|…:38|HqฯนฐhPษ‡jŠN#ูxxฏงส,wƒƒีUNฤqX‹€ &ˆเ>ายอh|ล ฮHร๒ ov‚ถvH๒ทท๛Aจไฯ/“ซJแว ]+w]9=โ5™ข฿๙_Ÿ ฦ)œJฅสฟ9ฌ่}๗ก‚ญศศ‘ƒ„qึ•6o–:๊๏E๗œyืถjี0˜๙™ซ˜TญVKหK{ธ ึcฝZPcŒ?\ชอฯ’ รฒ,a*{z*W>๏[Jั›อ่้v„พ:ญะ\_U‰*ซ|QaZNYษFšรI ˆRP’ืtว^:ๆ ปใK๎ส๏ๆ/็-|Oุาฒ)hฟsv>อฎ`%QJy(0(โ–,ฟB=}_, LSKUh C‡"LmHcB๒V5…A•gอค‹1ย•_@1Cnฤ‹Xภ‹,vใฅhIภK5+Gd่๏2…ŸLคถ๖วXุh:๙]Q๐๚ZiFyu2f3สjฉโtQq๐:ฯ@yญด“ฆ—” :[๒๖ึ<6oฺถR<วoiˆ โDe‹HถฦjรจXลคQ๖s`บŽG ,เ/V7ุ˜S3็†l0ผ)ืD8ฬvฑ,†Dv Br‰C+fๆ~aืะ?ฃศ‰X>‹๕YG IpX@>:GDn๓๙สŸeฃ2ศBŒ (ธPe์REi| •)คป฿M_ุา:^gp<0Ÿ6j‘ํฆลƒ# ภท๗aฯๅVํีDญxš&bXกช๎๎สชž๎้ซTaฝูlM๋9ซฉ„†—•อฯฮ†โ›วบแแ๐๐ƒe;ผPŸาฝG(ศ{ื"ๅ5าk[ืa!ฃถล์:ภ5›๎(ำoงvž„ืNvห้ฮฦ๓ฉ:ฮ‹="(ฅ8`vณ˜รhัm๙™฿9็L@ัhฆ>bOe๋ง|ค ะtฯ๒Jสก•jZิิ8B <ฐZฑ†\ๅฑ์หZ0z|Vโม๎๙ท‡ฝ{ล๒๒ฆเ€Yา)ป้_>฿แF…œ%V+G]‰j฿p9 ห๕[?๗ยm #ฅ==โlํธ๎Hg[‰ŽAกwณ๖้ขu7uLdำVSnหฤZษฒœ0ฏ<ลฦT–‰ิlj}ฯ™ฃ5็–่ิปใ7'ท’d้ซA๕รขะ6ท {8ฎเy (๙{‘„>I๐เŠCšW๎y๕อ๏บ๏mา๎ˆLmF๖Œื4ิJ[/ใhฃf%iฐOํผpม9ฏ†„-ง bqLะแภešิ@œบ;2fcoPเฝ’ึ๓:Š ‹ ขสc#cLyฯGTฑ6๚ฒํm[ฉ8hแ Rl&.x5$<(้1ƒA8๚ƒฝ๛ŸUฏผ)ัOxsdK๖หพ˜^+m๋ŸัcjR’ฃlXฤ6ูฅSdเ3teิ8^ฉึeAฟ‚Wำี๒ju_ชbขR"-‚%2E~S6&k Th(J๚ฐฉNaห˜ฦ6eGœ%มื‘`“ฬKˆdึ’@’ ี๓์8} ด่อJศšฃ–„นTuวภล~#พ[Fำ˜]DVิฯb+†๏ฏ;_wจ) zวo?…fฑˆ=฿ษ /"เๅ^ซา{ช6 ำQ๗่6f—ฒ๗HyŽผฌ€(C|ุ†™ %[เ“#๑l๓็‚ป6๓ฆำNjŒโ8๎”$๛6cxsPศ๘Kํ~ฦˆh$"ฒุ} AUN†๛33ha"KMสD, 3ๅ˜bนrš|.รรOo฿[‚ฟ%้๘ื‡้ต|ฟf_๒ีC›k๏นฐo>๔pสืoๆื่iŸษB7rkU:j7‹ฝ`)0'ฬ็้อGต€ฬฟŽ’-#‡P#้U{'ฬ .์ศตoฆ‹CsศE๐ๆลเซวœƒw9[=<๔=–“„๕Ye"bPท พ ๓นZฯKย/ฯส…cŒFศ#dฦํ\)>Fศo"fjู/Wฉ2Jb์U๔ปฑ(ธแ๑sr6nฬ๑M](ฮ”ฬJ,<:ฑธ8qูCๆห%c ถเjY๔…ปG‰Œ˜Djkท†ขืMฝUฆOn7z่ฮฏ@ป้lใy๏ž#|Hu•W์9ฏหeทwaงw ?>Gqvกo฿ ่lOŒู'pT @ˆy์ฤ6 ‰ฑ๗ฺ9ฬฒยฅ”ฦ˜ค?EEMŠu(฿ฐ)F0ๆ๋ pคŽซN2QYœ๚เ฿3gwํ๖qgม‰9 วUpป๓eoุ”๋{ป8ใšBก03 ฦดŽ("X–๓ู6(FฤŠ๏=ตŠ๗์}ๅ์zฑ๓ืวOมž$(,Z๑ฒX์]}7t"™—ธY‚5ZฌัŒ0,{รLร๔ฉz๙ dท;Ÿญ ไูh๏๚๕ฃฃ„ฒnžž๚๓–Gpล๒oๅq๊๋’*๊j๐ก{E!ศ]žŸ˜*ซ”ฅ" Šส…้c)แHs,ฎwk„gXj’ยิE~"_฿“1–ูมV^.VUY;=อๆ›าUƒ*\ฟŸ~๛พุšส_g gศM5ีฎ‹ŠS๘เ๋Œฮ๗!Bลa์]Tษuธโ—‰j p;ใ็ˆถ~\CU""ญ฿์๛ื ๎™}%ิ3U้ ้ฟฅ&Rฝ-แŸ› ==โฉำนิตŒบWž7išCฉ้†ซ‘…ูY๋๛LแซปŠฬฏ]^ ยๅพไต-ดฌ๒nฅถฒง'๗๏še’T*U’ซiQ0?`%Œ ƒบมิŸฆ_๚Šีฑ_ฝ4ำ K'5งี7ง%ัSquฝทฝsั}j!ศQ€ ป๕งŸ? Sฎซฉ‘^ตXถต"๋_ฺ,foฒุ_pฏดฝบตฏ œ‚๓ภb˜7์f๛}] ำธๆL&๏ศษ๏ณgo๚ฺ|Cส5=่ ›ฃj’๒HIเ^๎>(ใ.ญ‘€]‘Yย8RฉJ*ีมuC”ƒ™ƒษฯcเด่Cฟั.Rw๎แ NƒhธdธŽ ณ^Yวฒ๋*/งฌ)O‘ฐYืTž‡ฯwœวy0>4ณgืœ-๖r˜ฒ้าlค ลฬE,นg3ป97ฦL์”ล@3q?k๊gM{dก ี9สœ๕†0?ก%,:|(์?แน†ŠบlฃโลW2œ'ใ,lฤDณ++โbKK‡lƒไใบ๙ฒŒฌ 5ใ๘^Oi๖/๏สงLTะฅฟ–šM80T๕ฏ”ฉ<(fงbงk—‹tฅ‚—›๕ภkา|ีA žป ย-1:AวHa.ธอ?๐IRใ๗ฦาŒtUํฉUA}Aหค๗ฏ-5ž7€AฝAkฮ[ฬ็ฯƒhัสฟ[์๛ร–=๋ฉ#uู[ฃgˆc/มคฒQ™[ส็c2๗6@p๊๎๖ฏž~Zœบ/ไใCybฏ•ลoR‘๕Yา้ภŠ:*‹๖หa ์}3g†‡๛šOšpuห-k --†Wฦ฿zkผdืษ“ปพŒ3ง2ลน}~-pq\[~qุไ}tฑฎZฎห"?7zRบฦ\48hฦ6ฃ‡K?๘E ๆ1 M–ZŒy)V j•ฺ„@ฦ๙ŸvŸร‰?ษAภŽRVr๋ึ;ฤo๘๕ื64๘โบ#ลK๘บุัpข3x7 nค[{†ถ;D•YกPŠf2m’ญ๘oNkJƒChฦnงvศbยฌŠ๚ๆfC เใa๗$o,แรฉIŠcŽถvAwบเๅ๎}uu}DF("ใฏธกdYQฯo?฿g2‡—นูdฬ_<ทSว\ kฐ`_ฤ†›pิ=ฑwิls ภ‰ข(rNฎuA—๏#›']?–?>"ชbฒTษ_}ชพ*T๒%y๊—5่HŠ^_ฐไMINH)ฎ๊Y^ป–XT๔๑žณH•๕๓ืP๒Twc7g’+๘ฉIยU^6*C :ี–ซ:Nห%๔๖%pฺ~‰( ็šเ‚๗ม๗fส–ฺนนuตw ซ[˜e*บ|ิ ˆโร7ุƒdท—2๒@ฐ[ขv T[ํถ“อv๔“7 Wธ`ณ^‰dƒสฯ@หฆ2:ร็<ฐ–ธษพลn็-ฤl–…๖‘๒Vฐข่I"ฟึ)ด์Yมป5%F‹"F˜ๅ},˜ Uhู฿{ฤ~ญŸ๒€อ8cฐ~šaมแrๅƒฑX“tศฐึo\aะ๕,ฉ)ง3|ุŠ OD†ญูCๅชe-ฬjรuตss–ฺoเัY่ฬmt?บC5!ว์มxศ0,y+ˆแ˜~+๑๚) 7ๆA_ม|๚ธดO6ลาว฿q`เf0ฤ!๕ˆ"ฺ๒EัดfจทŽจI*;งupMŸž}7ีฏ™ขlฝ$ณ๕w›3ˆกภ“ซ D”ป-/~T',ไ&`vํฎฏ_cA–ฬ‘๚‘ฬpไพๆEc*;}|H„ทภ:ทGสlO=ลBYืซจjฐึ๙G%ะ๙ฃ๘]์JI๗Zกดณซ^ ึWนŸ]M.ศXwถฏy \ศฅ9๒ะ–>^tๆ’ฉ๐w™}Jฌซ๗ํญ;ฏ6หkไ๊SC3่`U)ZQนูgืGj”šH8`ฟฑ‹Cœ๋P๐!ั :ุm„ดFŠ€Sซ"ข#ž`ญ*uJE‚์)<,ไึ_๔ฺ=t?ไo๘ึGณอฯKuค,QฆหฯีHฉจmฆถSyvฃ%ฃ ก ฃk7ุi{G้ณR[ทเYศˆโtึ†ะ๋โ๔ฦ GŒŒž๑ฎZๅ…–-ž+|ะZ1mฃปง1ŒWฅEKv;฿ๅพec5-5„ภฬณใ'แึš\ˆชดืMcป**+บลหzแ5๛๖kq๋0Gภ+Tgญศ๘s5hŒ๛BSก๊5‚๊ืชฤ๗6nW}qง‘ฆooึดcด>„iฎiZmเธฦญJ.]MนŠ+็ฦ@ฯดังJ Bฦzoงอyหส_’Aษ‡h๛ใœฒSž๖&ศ„ี%™•ธ%—%‚d!t™—ะ่†‡ม nบg $ใmSห๖ฌh ษ]&ๅ๛'jBuเ-ว็่sญฆFา—ตท—/}eมrๆ๋ฑˆิฦ&rC‹ฉŸ~๚สมVnๅืˆF้๏๔ŽpตฆP๙ซŸ“ฉ'‹[ฆ˜ †ำ&ƒง\>w(ๆ๘‰< ‰Rมˆ33ฐน๒5ฎบ๕ัQ๖๎( ˆ Tš Q™ ๑ยัฟ!—ง–ํ/ุKโฤšˆ™ ฑsƒVชyr@ฉ๙xšCผq๓FŒ‰…‡Gํภศ&็ศ๚‘‘๕>ฝIิ่g€ฯข@ๆS๘คใ’ูIึิXฺQ™โ๎สดyXhงษ3โ ใ๕๛‹จwฅด!t.„ื„G›’Y้”ก_„+fๆx๘ปv{ถพำžG†tฉLL&L=2ฎœ๒๛']๊ศั๑ื–๏๙๏GF}^XP{>jๅ†๒๖OeG๚œ}๒$(!_’Žช็•+ม y8}ดD^ซฃเ”ษh|๗QƒŸๅjผ\I”๑หv™ณํE Ÿฮง^5#~ๅ‚p๋yhNไฅฟฟ!ˆ"_์ง๗_พ|ไ่`๏ภซฏ‚ิพSภƒ{ ซอ›~ะณMlzเnQ;ู๏๔}`ำM์ลiึยxญบ#XCอ…^ %ก ก6ึH๚ซcฝู>~|V_U#VV6๖){นะถ็ฤ็๘Pวh„kไDŽu˜พSbR๋๚้๋GŽึfJŸLŒqf:cŸ,e>„๎ษ๙ฅ๔K๒ฤuŒˆฤHฐศ —BH.[“ ‰5’>j…OwWก๏ืyukอ]~v€Uฐซ-๕ฐยฟt`kั๊ฎpถ/{}vew๗๑๎๎สœฌuึ‡นญlK*š3Mๆšฑ}หฮ“'ย4“vค\[T?๒]วdส KีTสRฟศฉb^=/™—อJŠ“ทt๒ภ;QNๆoGKี:›ษž‘ๆ”ซžN๓wT”&Mอไ„เˆœฤๅืฒ$ -ุ~FHjgฮˆเ์•š€.vaฤพ1คฺIZมบo)๑•ˆj{”œ5v[44 b<ไวฑ_ƒภ’fค, „่ฬ™ zๆF้@iYœฑ„z ฃLธ0’@šนค*U/ฏšชซ›ค™Lnw}=;@6ษW+”gg ุโtdํษC y$„$gD้DŸŒล’ฤ๋$ wง‚ฐจ{N’ฐ%’6Z i‘CQQ}๚ฌ_ภ‚ถ†UZžj>ึ็๐šก*า™ ”"2ุ๊เ|ขMrbก กิ@sIค|คแEq@‰Wก* สก ƒŒ]›็…)๗&–†„ถ w}๕XUถศบ?q,ฐy็NPd|™%‘ Aั%ไพHผ.n๒n ุ| ยัพ0% ฒมzXฮัzฐ‡e‘ฐร@|Ž5"†ฯŠ\ธ้ั2‰ฑฃœว!zฤ฿ ๋^”’)eQfŽaห?วซฺ 4k~‡คTfคิอ•?๛›_ ŽŽชื1๙๛{ R•*สผ˜HMiฦฉˆŠว•บ+}ฝิM)'ง"#ฆ†'•SทknBŸงVญึๅ2?†šW*+ัษhซื˜ฏ=SนถJ—•Gชั ส8ย็!U*FไF๋N’<_™หŠ„๚†ไE?v?ู,kžุษ”T‰9ฬท aิG๑ฝc•๎ำืิ˜k๔Rฒ…d"LBWึHAw^"“)SSุ’m?e€"^AQฺOŒ–แ› ั ญ@œึฅ`8FยhศŒำ9gNWxใnBำ’ห๘๏‚„Lฺธภ–0’ฯ!7ะฉ€X!Aฬ]"อX!งz(C‘1V†๛n๔๛แxdู ฃrL„vึ‰๐ Œ!e“฿ู”%ฌึže๚CฃYฝ8ๆฤี%ร%•3B)SŽ~ซ<สม@Rอฃต!1,ŽxOด๓ฃ…[ต-๋ งPžดดซœ<๛ำงู1ธผฺ฿็ž์ะ˜ธ์ดš—4ปIไ–ะาucฺชF้6•ข]6{Jqพฆฆต๓ฎbA]‚ฬBต <-ฤ๔”uม๎ภ ‰ ฌahฑภ ˜€๚เหBC^š๔JH@i^เ,เฆ€4hWบถLV/gกaสFฒ๒ำžY๓O‹]+ฤฎ๘)6O๗Zวaฮ/ฒ„%Œน4‡8 หฒ,ร!ฎ_{N๘กWtšๆe๛็ชทe ˆ๐ˆ“Tีะoeแ*๑–:ธk–ธyษŠ?›kใŠซซ_Vวธ}yUไ้Ÿ'<ๆึผ3๐้‰ี i,ืSซ]ษU.M๛uนvj!ฯgำtื๖,_๏tฐเฝR’ๅ]ธบ›G ฒบlะCฌ‚ิา่ฦำ>1|Rอ‹ำ๎สข{„้efD9รธญ ฦถsมจฒˆยตญŠ{ขŸŠหŠEq/ข5่ฒ„ี-๔ƒ)‘=‰๚U(ณ, ญZ/Vแ1CS,ฬ;F}‘*ซคR_Qี๙หOใณZr‘šิ๛ฃพPอ.ฉๆ๑|ฬญ8๔า๙tด์ฺ!ฏด๚vฅ%'ซจนฺrI็„งสR“฿ฑฉGํ‹Ÿ*|ะปc๛ฌ—ช‰ซธศŽ0V๋3กฮRณ>๛ำ่่ั”˜๙y[RPU“’‚†๗ณ๊_@บ’(ีแyๅๅyชmwทๆผ„ฟฯผ๖tte%f?f"cํว'ลp.;”;Lcษnั๑I–d_€ภขs ์ตS^บ97๑๛ํAH๓lมณ1Š^Q3ฅ™ฤ [šรJ ‹ˆF~2ฏ4?m:วว;qฮ:lhๆ X็ำa%Q2โbำi์@3Gr๏}†‘=๑ ๅญžQรๆ๖ฏR‰#P๓ หฮL<‚‡๗ะg:Nั"}สฑ ๊xNbลฺ่O „ซMยปm๖๚ผ•0„7ฺ8›_‹0โUก‰;dxฏFภปE๗L†fI_X๗pๅฌ{}<=ฐณnข"'ม‹ea8™5Q๗H๒<ก€4ylฑ}งowgj 1ํธ ๛)คฤaคฦ(sn๋ภผfVดฬผkๅ&ๆ-๏/,๖{๕ผฆyฒ-ตoฒŠ(*|!าฺ^อัx#|Šy๛VhYxธ.์็Ez้†VdSmrฐใีƒไๆ/\Uสพฒb7ฒv๑ฺEฝA9ร`Xๆ6า ช๔#ยฯnzcฆ#i‰[ขษ๔+$๊]’–๚]( ณpd&๓ำ—ส฿u^ฉจƒ๗ฐ;@jfข”k็ิ„#•nสตใฒบt๚ OC[ย‹Jขไ˜ธ๓ฯแFถท.:งธฅฐJ๎4ภไeEซ ๒‹Lฑ๕uํดฏฟ–Vีื#L‰ิพพ.ั๗ฝW/šภ๖.i5FœวXnล‘,8r๗๏&˜ไท]ข6มัซต๗ๅๅ[ด9ๆฎ๘‹Žœ&r|ณผํ[่"B๋๖GbPžoฌUpู]gถ†”9.K๗๓“AM OAl‘wณ+๙งไFH•™4ข‚LD๋ŸOฒ็cท๘>V…l\3.๋Š“ญฺFu‹‰ธฑป๙‘ฃศฉWtkn้|บO*ฉJYฅฆฒ๏ฆf2ํจŒ:š–].ํ“ๆK{ฅ/K‰˜ฯIŒc!ท—Hเ†ูŽ’wIุ/.J‚[Jrฦz”lJ^๙‹ยง˜`Žท@$ืd๊จ0ๅpzS=D๐dขlมZฦูŠkYO3๙๒ฝ" 0yฯฃs%8&6[)Šฑา๗4BXS:o๋ฮั|บIplŽ{wธŸw`xtgb่็ก๎PoจzE.„sซ้LoJHlJ'้eI‰e้<iBpP.`xFฉซดAoq•%ท7=.ธ Zณmูsˆปš๖%ต5{ี7อจPผูmตืฝD็O}ๆ"็zCnไืฏ˜~)งพ†Kข์๒rŒl ฃ‡kฮษ4j)œส๐แแ—๙9ฒYูeู‚์฿ƒfซฦ๘žืรร6S3ขอแ^[ธ’ จp#ขZช{Hmพ‘ W„‡)$$แฬ‚A ่vŠฆ3,– ษk‡PผŒ\ๅๆ,เiศ ย;ขภžยlจU“j&ฌิh๖ฏ-dภq]! eย™š5Q"฿ฑ๙w=,ถต]ชซ’ฬB$Uช""5‘=3๛DๅRิ?ฐTฤVี ฎ)ฟ|Š–วz๕dEอk\๚ๆ๗ใภฬCyิฬ ฟdyd93‘กํ9gเ‚{`W๋hjƒ“!o L๖๏,ฌ€šด_r*นึU ^ป๎S.๋ฌbkeŽ9lS6Cax‰กdื๎’p7:p๊ฏl๊า7กoy๒"๎f…ๆj†ึsqkั/แรบฉ,๐กล[ัข๙kiLใฮใB๖Mp๙Cฐาuผ๖x”๓๗&p:m4Š%~—Nเœ,!ฤ๎ueถŒL`%ผ<—ศศoˆคจhสฟ ˆ2•J-‹~6F ปฎ/\wดl‘๓ต๔ซ๖Oึ๙ดฌRuืv้๔สธฆg2ฆizฯทMห}ฏง"†=$๛/‡`LwำT„I๓๊‘Q ๐ลŠC ]o„฿ญศŠ™{kฟธ@GSdAJฉ1อไ{5๘ศย!Flปมธ๋๋ยZ๕ึ…1‘‹ŒkM! U($2ฆฉ) 'I๓ณ์๗YB๐"๒Z‹ไb=aงCŠa๑กา๛๓ธ~1๙(Dข๕๙บD# iwร๚7”’Wƒร tQrPgqฒ“ฅโŽุ_จู]ฅปฉ[%‹ ฅขฎžsN็ฝ๓๋๏๓••yฝoผ๛๚^๏XY‘ ฑA๋\?O[11 ม์7ZKนีฟ‹v๘IำXพ฿ุิ–ฌ๊ฮ์=U๛hbOv็/ช~;๓€XDขv๑?Ž\วจˆ๚€8*๓\˜๘ ‹ณ•ฦ)๒hง"AฯฏHว‘๋9+ฎM ต^ลฒฏZdu๗ƒ@9}+~3๗,ญฑ<๚LYฦXoโ,งr‹็็˜„zŽ_ลT‹XdŠ‘“qdั<™UHp“ฬMcเกH@.ใŒ]j๐‡ั็ัW฿งuงy'r‚ั"๓g8KTQ—>ฌฏค”O{MาAฝk“Fสงณ๓ฒำFฦ€วธ_ˆ[KrXทฆ1Xา^ผ5DK_|BN{ล8๘zั ™|U฿็&๎ง—Mืฦภ\•yZ๏่Vt;๔ง3ืฌ>NŸ}ๆš๛‚[J#๛uลฏ-~Ÿ๙โต}F๐ฆzข< Eอ\jฮJ‹[’J\ฤ‘ภ฿^ŽหŽสฐl๐A‘Hถฒ$Ž2r˜`‚ศฏuค&’’h<ยE คTมฒ^1์ดt฿(2?ฉOD†€ž‚hฑ,%D‡ป฿โก0ตถSjํแ๑ฃ'&k_ ชbาฑ"Ab@ =O‚ฦิฒฃf๕ถ๎โ˜ฮ{C๐žฅฦdุpชnมโมฒ)< e!ฤหabฤฮDคiฺศส&)3q„š”ั๏ษ%cะ๒ฟ5‡`็ูE๛Œ>ทJ๎=ฑ๘ข ๗ZJ‹ ว L: พ๏€{'5fkwb2†าsฎวชt๙=ผุฮวืsšC[Iศuทฆ}]|3๓๋]ึW6โM‡zNืdล[์_ŠŒฏต๙‚ทเGG7’#}ฝ{›าu&Kgส”ท๏N NEss(58i๘พ๒ ™ขLฺจV7vKร*!j O^k<“ๅฌ๛ฯƒŽ๚lW์O?๑Y†F@ะฅีสธu5&—vษดค5นฐ›uปŽgB/\ศ๖™ํ๋๋ฤๆ–ฺCsL]ฟ(xศ ็xoธ‰AฤJs๘งK9šqฦพี^l|”แ๎d+ฌฅB๒ซbธe Bg๘#ขŠข"๓ฺ5๏ัสผภ™ต^]ปฉฅ1๖ป†ุV_ มwฑ/I๙k(3Aึ˜˜ูM๛{ส3ำ 6๑๔ฦhM#UzD 6jVOฟโ–๒A๙๎๏~ \•Ÿ<ำุเ1tึต;ํZm-าo[[?O7ำdšYŒ๕{ถ›ดโญf5RซีŠ:&Rฅ๔Q2kก"RฉิˆŽิผrœ ฤฤŒVJ็œ#”XŒ™—ำ/3ฆjผ๐lqยIพ9ก -พyผK˜oไิศ–†ผ็๎žK1ฬŠœณ†ŠV๛Uฺ*ฏฏ๋/ฏ<6zๅท\๛!8๊[—’สอŸŽฟ4ul๎่ธg—ฏcฟ๚กฺ; ›˜อ๑Exใปะ๕๙เuxำ…]๔b‡ฏ๘๛ว^ว๋‚o^เ*ปyำzงณœฒฃษ๖[ญ,žฯbKพp๔€"ฟhlqดๆ๗ชhฅ ๐|$สž๘๔‡'˜’ฤ™~fhย๔“ฬกะ„บฎ ไ˜›7c…Bแ{[ซ๋สž้๚Aวธญฦ\“ญa‰‚ธECะh‰ั{๖งŸึ^ถ่HมคHqJ2žZ[๛^kฟt„ผฉฏฏD฿!4รbูZcฑ’ึVJ?/L;ฑ#6l๗๖}\\_ˆ฿ทฏr{“6cรํŽฦฆ ษฑห‚dฒ.ุหบะuฏช/ต^๕ำœTฃอv๓๓yZื|/ูทO$ชี๕=kี๙๙–dTƒฎš"ทล{ี^ถฅลว—3dฦ๕7(ฏฅVูฦ—าx…แื s ปŠ~ุจเำ^X+7\ซYื˜W,‚เZEb‘` l„ล''<(ฃฌp‘ฬฒฌแยำ‚_”ฆŒ;‘sQ[หy$H@า„ธ๙ะแาฅ~Žp.ืCณ%r+วqs(;็Kมมคำฆ-Up˜3ห]ธw^โศภฎช{ฑCzœน!?ฏชป็A๚๚U9า๘#ช#šทฃ62n]์:ษ๕๚Œœxๅ2˜ฑ7งMF๎5U;ทEXraป๏‘๑ƒFฮหมฝๆ+ Fdฌ้ฅฉ๎|NผIff$สp๙ึOะ}ฃ^โงอYงฟเf„Qณ\ฺ๖‘T”"†3 Agฐฮ]V`ฺ‹คY# ํqฉX$ฏ2‰V7P71๚PœpkงนHูงผJ63T\7ัƒ่รZpˆ •Pžx„ล”ค™—๑l›@sล๐๏6[ ^Zo›ข๏ฑ{z•xหSฝฤ[์ๅผฃ{‘ ›sใp[๕|เ0ร~MˆฒqJ์ปf‚€‚g๘ า'žS=__ฟl็ๆ฿หํ€ฒ1YbาTRข oนYฏwOXg$จ6YF6F๗%^๏อตโ›ะ`ศ๊‘ขแีฐ๓ล(_‹Œศ๛๚๛ุีt/c;ฌฆkข-@ฅะMm\\ฬ=๑3ข8-ฮˆDิTœbNก๓ฬy,o็ž;==3sฬช-^ๅพ64G:^wฯ! ‘ๅญ๚๒Mf๗phNก-`„วุ๊ๅ๒ž9cฑxต ˜œฮ:ท,ZN5jฌmึ€bฤpOE]๓‡ศศGcธd˜58 ‰ใmถฯฺL›พ8Œฌฆโ‹ ู์์~Eฬ1ฟ`ฟ ขม๙Ž%ํŠ‘e~รšIn ถA]<ํeฝ u็7งWแYูx<๓Mฬ;T1ด ฃณ๓lR< ^๛ฎ&Xž๑Pwๅ๓ฦ#Œภ0!|ewe, ฏฆฆƒDaFgef1‹g=\ู๛ญญ๛TถzIฯ`ท)กำœ=ฏบค๒—@‰?l0Ÿhฌเz;ธำ"~๙๙อšžฟG“Eƒ7๘๏ฌ,ึeฅ‹—|๚ฃOซแํ†ัหฮ,‘ƒซ7ทWณภu™$U‘Kีคฮaฆฮภsฮ‰bž˜ฉแ#'vntูัฝ๑แ“O์CŸk!ธ=Xฌซ๏oน’3y฿Œ๓ กI฿ฺj๘้ž{๎ฺณr฿ก›เy‹„ร๓s‹qžƒV`DŒB;šํ๗ฮRpkZƒิMxถ ส5 ฮ’+Vhฦ)REภ„ท™š^‚๙๎ญึจ*ัeUoi]X€๕‰€i3ศฺป3#‘a&U–*คjRเb~โั้–‡fพ%‰’ห2gh(๛ˆ‰2ีท„ฯฆUj"%ฟ๎{›Žl‘%ส&-อฒฐ"ฑัX #์gขgฅุ/…ศqœจ`“ ”จCษถ๋D๓ำgฌยJพ\@‡UƒRtPeีr}:ฟ^‘โ๓ฅ(ๅัืƒ๑[Š ๋ั๚9qWA}„(K}@I1๖U๛ รHTŸมFbทŽ5*XW๙b็ใNูิง=ค‘5๔ร่ d%ำ๗EดฮUค%-๔Bsิจ‹๖ˆvTฎ)‘\–,OณŒฟJธ|๙ ๚ผฐšwjฯเz9ƒ#ไ_ป% 4hแk)@h%…Iน.๖BnSaส‹GFวชจJ8Vฎ4๎ฌ3๏\จทเ:K;aOlนฉรb๋OhXl|mฬ๒sื๗ดš๚+gฤ้ธฟฃ8Dศ@WW๗”๎œM›u8\ๅš•™๑dณpŽvฟ8EUpF‘x+Œ@4!บ๔‚ท๏O-ŠN&ฒุ2ษขื~ำฮ,0W}Sหบฐัี%ธa๐L๔า—Z฿@งGฤ๕๋˜ฑRิ่ฟพG๕’ฟิ๙ไ“‹ัc>Kจ7ดึ7ๆNฺู:V9ๆฐณ|Vฟ๕฿Lใ9ฝพฝ๙ุฬคyมศฺฎ"Fพ๑นว1ืl วึวrŒs‹า˜฿}•KๅJ๑Bm[ผŒlfoผ๎ทแแeกปF2ำ฿ป ซ๏.ปb ง๎๗0*Nหส_ค™คdc"ฮ 6พ}๕ฉสพ๒๊๖\า9เ!ฑC่ฃ#รdธฯทA}ฏ๛ 9คlkTฎู€uWuถR%งผฎ-มU‚D F–ึฑฑ<ๅVNn•‹˜รย7๗mฺ8%;ŠDŒ;ผf5S”ฬŒป/m์c+๗ณษ%ใ˜ไโ‹๑’y‰ศณโ/'3qK$๔นbUDJโ`w{โพญ๒”s;jPฯŽ์eg–ั*}หˆซ?๙๐4>ฌn1HAบšฦ™ คq–ไ“ ŸฏฎฎฒาGEฐ˜hŸŸๆ้U<ฒŠ†รoฬ๓ฯว๔>woฟIอคŒฺZพฮ,A€#ล+ฏ—ูV|วณ๛ภdฆ‰zฑฐ฿๊@องิœ~Ž๚Tƒ5UxC`dํุฦžย๓ๆๆ๛๕๔bร]#ฎ๛๔ำ… 6:›j]tWU2)K*ฎกhภ5ฅ—ฦŠM๑้ลยYCBำ‡Žฤย)Y็ณสณโฤธ,?๗้ณ—์n›๛ับxdA6!@œ‡ฉŠฃC6bำฒœ!”ใ๋ ^ืDวLโ้†้\Zfร„—๋*ภ›วถฑ=ัฟ ข฿U๚ ๐TรTnี๓ุ ฺ๒ไ3ั}jบา%“„`xe‘.+Mป€ฯฬ~๒^JŠ๘6๏ฉxIื@Vชj๏จฆ-;ˆี+L้C๊‚Vึ†ึa€["=ผ๎ ฃ(\๒ฯึ(hxฤ+ฺ˜7(€Œวj ,g bกฮฑhะp›ixZŸ6ฎs ๙ๅ_~้๕ห๐ฐN โLรˆ:ํ…๖wแ]ื2dต"†1บด˜.วฬห8ฦ€/ืjอ™๋ฝ—‰่(ฐ(`แ& ˆทbCัธ‘าฟ? ูYิซŠหฟณกCฉ‰ “‰8ฏ™๑ฎ๎/ชŠญ๓Uะ๑รีสํ๐3ซ>S #\;ญฟaœษ๔อแa}ิศ0D๋xวƒแึึ๒ฏใท ู๗ฮn%p\Œ๕ORถP˜}๕ั/ญ:Vzaํ ๏๛ท`Q …eaA r›ฮ<' bเภZฎ๋๏x๔ฑดณ`>๚B‚ู๚"๊Dฅ|Mฯw{—๘ถฆWฏืฺ{ฺBย\Pหพ("Iv๕p฿ห`š@๎ฑ7“ฑรh’VT-O,CyŽ ค๙—๕]ผ ]Nป๏OuWึไšใำนำ<ึB%ต ฎ์2iPxช1={?ศDช‰ต”fe˜0u]๙“ฑ =s$3ธ,‰Q8ov๘`+ |)3?f@dm๕9>x8ณเ๕.„5ฌฤm3JมสนD‰"ะ ษ˜.Fn^๓fม,‹&ษัamฦบ,ฬb๖u‚u/โท9ŽทฒหYิ๒'ฺาฮ๓&ปศcฆ‡6)bรฺขzฌฑ'tด"ซฒฎ^ ญ m"kตq๙7ศ๖&ฬME๎ิ›Iฤ'ฬืืื‰ิ=Cบ‡ฮy&,ญ๎Mnp:ฮB๔>3Ctษจสb?—nค7Xฺ|sฑu^2ฃ~d+๕ฤVd%ผถแพi6^# IJip,๐ษฺzKcc]จn็ฮ:๙ข>AXฅ™B^rขขๆs๋6๒`าณ฿Ž +า€มษ–›œ๖*๘Kš9ไทV๔๕ฅคใทWqpT๋-฿ฺbซ$…๔ต I.K0Bๅwehีo˜ขฒ]Q,รZซ๔"ทJ๘L’K–๑žˆHภ"f๚-ฬฅส`๓ ฿ซ๙|บ๏[๛#้Hัˆ9หพ ูฌ}ฅ4แW๘‚วƒ'8ย๗แ‹งlSฏฌOญถ;”D้KยsU๖}Ž>ะcwr]AwU7‡ ‘)‰)‘Žิฆdi๙_Ÿม•C‡`2๓< ญู้ัŒœ’oZkOŽศ†ๆ~_ปฟ๘hฌฎ2๑žิิ€ฦ่๖š์;Ÿั\‹'2ภi?Eฺ qูหล้๛{W1๐ฃ>ณy›’(๗ว,ฤฤ๘wUๆึ™฿oŸซ๓ดA฿#ั฿ํ๚)jœ;c2ุ๑Ÿ!o์ฤฮ๚‘sำจ‡>(?[QแcmฬพWดu๚พi—IแR'–™อŒ•qฺPขบhY‚ ควาŽข๕ืธqว ัŸy!90๙S`ย๘ำj[ ‰y๗†ิงมลฯฯ/LฯœงjงKK1b๚/่พL‘ธE|^&฿ฌ@E“ฦ,๙gƒ‹–——›<โ๕ื#3}หœฆ8Gœฮ^9๖pๆ‚Œห—dํ‰–ษยkvษ€ืQูW๕ŒV?‡ฆ&ฉ?|ม-”ุฆFvฟtŠท]š‚8S๎fSอ‹&+๙Kehœ™`84ฑB‹ฎั<ฃ๊นt๊ิ%')ws^9นDNšงฆfฌซ[ๅ,ซ,xชบfZ็ฑ4(๛ฺlฑู,ม"ษ^^+]่ ฑ>dวgๅ๎@WM46๖]ไiฟำ๘ํึปฝำ๚์ส ญu๔lฮษอr—ธ‡6ŠŸ“ล ซG’‡‹†“ ๔`ชอมUธ๑!ามž๗Ÿ:ล’’Š"ฉyส[el๋ซ\_R๗ ๕ฤŒ๗ยvฯศšzเTEมแ ยs๛&ึ7k’0$D&WŒล ึœจZR๕Lุ!dB‡ย.!๕๙M%Ž๙}XŠ์ืJ23[œฆ ๗ด_T‘ๆก๘๏LสVY4ผ)ณฎ\Y๓Q4 3ั๊ฌไฏื_๘Iดž๕ญฐHวzอŽ๋šษ็๘จํา€ฦ€†ภe๖๖๋iูฏว๛ึŒ~๔I‹(ฎ E›6๚’ฒ&mŒ"2๒™5ลอ'‚HลJoฃ1จoๅXe^‡9ซ†Zืฅ[ำ‹‹ชGซฟฟhŸฑ.๐๚‚&„ถD›0+๚‹Š=น!ั@H็ำ…$eญ ฉฉ๏–๏žพะฐQขฐฟ{dฟ)ฏูเฆพ^L“^[฿ใe‚๋๛๚zว|้บa๐์HW้ย็}Œโ๓…ี์r%œ ไr]UฟqปฤQ ZจCt๋`Glก๕ผ ไ้ ๒ิ}uวžžษˆโQžๅ(ผP—#oซ\ป)$•,–}•;<๏Rd)วG•ทฅฝsฎ๔rิK๕{฿i%D Fฮ็ุ"ถ|Œ>โ…ึ‹ฎYโ‹๕’มž้GŒ|แG5ไตโฎ๎ฎBฮ9ทA'อ•ฦฉ&_ 85$ฃใ\W๙ฃฉT๔ง%ํ0ถช๋ึk–GTv™%’Fล]…ญจ๑n@๊จ๛ญจœKส++_YดqcัJฝ๗ผŒฆ+ฆตpาžŸฑaB=ฃŽwe9ใกJฑnร?~ฆฺืลLโ.eซปฑิปาอ3V rY~g3R๕NmCC‚JwS—่ฮ:ี๏7m~.๊š{ค๗ฟรk}[|[๚ไfg๘ฒ2ฤ๙วŸส85ˆ‡šjVฏ]t,น s๙+@%ชE3aฏฑ‡ชฦ’Nส๊}ฃ‹_๔mz๘„S;d}ๆ ๒nk-๚ม›n“ญPHIŸ๑ปๆ๏ฅ61’oRญป{ฃ>“Spฦn-๒พu%*e๙๙‰NŠ<7‰ˆคh'พ]lY?Qt&<ะื๗oSPท™u?ตฎY<นsเ4}ร“#Wgืšนปแ8Cˆฌฑ‘ฉ‡V'ณ่า›1NซภdXึ"Zf\นEฦ<ล…†Yงฐ‹ฉ ฮu)MV%>v0ํ๎แข€„›Dาธh9m> ื[ฝ]!2ใA†ey—vT@4รœ 8็;Hk}k+ัX,็~ค"ฮg=DŽยq1๓ฆb้@Š—dฑŠG^๒D\ผyซPฝ!yCuš.๓ุwŒฟ‹kˆFZwศ;๘ฺ|ใj๗๏๒k฿@ผ๓v๏๊ดีJง_๛-F๐‚ล็๎รWแ\๛้ง^Jี๖kืfGMก|ลั]XN'~‹YŸ~หิซC๐ภ>‘e๑ทVŽkc่ญ›ภ W฿ งผฟS}^๖oซฑ> _ฌbฺุฮ…k‡$]„‘ %8 YHจต›%bฒถด๖w1,a$0ื+_์ณาnไr›ข๚26g<ริฑ๒2ฺใ(ใ2r๓IVพค:๐zฟS฿%`๐Hสใ‡ร็วญบป๎G‚ทYŽ‰ฤฉฉข่ฒ‰ฌ๗ืVๆ๚ฅRqฮšy?k"KจษษืoB"ฑต•MI˜์Lแ=฿ะก‰1ฅๅMkพ) FมA%{ำฅrSŒฆ# $(8 ๓ไซฤ”N_l,(ฮ๛ซ“๊ มŸ ๋gส6m* Mบ#Š๐๓.W-I}Oสฦ0๔ zJ™๋ศบfญ9q‰„T้ พˆขาz‹`?็ข^šAะณ!Bฃช-^น•ไ๒ฑ‘6ƒกVJx+ˆทcŒะ›ฏ๊z.’Lฆมˆgสืด˜ค ึทฌ๏ถ*WNOm•ญบึZ@uzbฏ๏B๗RR?ฒvC† {๗Jา{G†~ืด4Xญ‚jมล€ฺฃน€6w@,์Š;์zฟ่‡ผฦ๚^ฝ็=ถqภq โfx๑‡ะฐeฬฦwั/‹J#•ีี^ฟxgซ๒W฿‚b๗k'ฆไ ว๑้mธํ$แo1B4‰ ศBZ"jŒฌ“z4ๅ‰5”b }ดฐกrf!V2็iมศ3"๓ศ4ใ๚-=Dข—ธ"oy๚/&ฆ6ฝ5>ฯz๔tข9โŸ•ฒ(&J6าญคฉyฦ%๙3ฯฐ@ ;hดa‚E’lL†๋รŠฃFไˆ‘ฟAภ๒๘ (–=หOฦM;6๕Iๆ%ฟห8Qv\ญPืฟT~;๏๘@K\ ๎ถฐ‹Fฑฦmฮœ๙๙กฝฬvฝ๕?>ฒm๓ฎ[?๑Z bฏ=<สgm“gาฯ4วุพ:ำึะฬ๖ฬŽญ!uฆุพV}sr“\๛‚๎๔๚บEไAเฃโ]œ๋ะวv—FOะๆ:•2w)W)moฯอก"‚_† x{็8ำอฟฒ์ถื'ฏป๏/ท! ๔z‚‰l>๕ัx…ฎ†็y=N.๚€จ่?d”ณ,3‡ๆ๐ฮํ$๎{Vถuึฌๅš๊U‚งงไž^รqล”$†qเฏ๕…์‹ ‘wฅ&ฉ€mhใ˜J‚h&แsI>๎_ Jฉ%‹| jฌ+tษd=< ™•$,‹อyŸผŽ‡ŸXNG‰n.PฺัI‘ฑูU๛YญlลํBบ‡ถ<~CซปeอตN๐0}บทตๅ๕D า๒Y‹“yผ)วผศ2žcเ/u>(หอ(x„*4[ฺ˜๏ๅฑีฺยKKญmึƒธ๊?mพ…'Bุ_g]ปฒ์“Ozyže๏ฟฦจ ภาืJ#%ฐง,ฦN>dูก€Sช@ญ ๏w˜๖p ทม'–uqฬm ;#=Gหๅ อ`บ= ˆไ้|ฃ†๑0dฃmPอ@ษ็ึ"kหJน'ืpe“ธ™AฑŸฐ๛Oชฝ)คาฯA•ณŽล79—nL@… น๘!ตNฅ 7๘Gjฒp\ษ‘M†Eฝ ๗๔9ึ{ฃ—i€ฝ์ฮฺeJข :น๒bฌฮง‹7ซ฿&8vbBปW4•{ฯฌฌ‹ท๖CถY็g…๒ ๏Ž์บƒๅุ>‰wท|๘๗ -(ุp.ะศปˆ\6"ป๑šฒฒZคึไฺ-l‰3frqๆืีYXภVดaส4๖šภ  ภณ_”ฮuฯƒ ทฌV)ฉ/Hผ›!gด๖knfXš††5E๕๕E๙ B-&s)ฑซ8=%ŠSำ•u-พ่rฦ8“ฝ/<&8‚•Tjยฑ(6๗ >ŠšิLR(5๊G1Kห[ฐ$ ก้ >ด‡=#c๚฿Emด’ฌOYm‹$q\… ้ง]_(<cต0จRwจษ๊˜Zู0็งภE‰rสุฯุ~#ใ2Š'0ๅๅณiถ4(๖ซโw|›ืŸ๐าฑ"ืฮคท฿ำ•lxkKูž€=MZ฿}{ฟบ™oหm๚๖TB๓ไฑGวv%ฝ}๔หฅเ;ิ็oฃ‹‡o7ะE ี{G;”ูp3ƒ>็q‰ำ†^๙ัึ$ ?ารแ๘q๏ฮc^๗ใุƒe0ๆ ึไ๒๒ฮ฿pปคน๙@39@.iถษs0(๋฿๖ฯ”™EมE™ัน‘้๑ฅษ™งPฐถp_zŠฦ”˜ฒข†š‚ฦfู?เhฅ’-์QRGำG•ด&’ZjๆขVŸ๙๋๚›ตœพ ฆฏซBู]ุคค&ำวจี#ณำBBlษใ+zตŸI)ฯ>*๓QMQxxุ๊ณA๑ท-9•ๅ=๒sŸฆห˜hœศศMy๏๐ˆf< คU๘ั๘ทvT?๛žว๛฿ฟ˜Š‚`mฃาMGยหOU•๚ป๐pํ‘ัู{ฤภ๛ŽญญณYYณc๑(ี่ซkซH์ชUื๓ธผญ?™๕ฟไ5x็ๆฒภฎฅ:ใ1นB„ฎ^aA~๗ถฅk +ืV”ึนX๊จฬ†l”Q˜'ยุ”ฤJk']„อ -Ÿซ[!ธqxfMฆ<(zgxrฤ#‘cะ% จŽ๚Z*5G –ํฺ™็,๐ป้่ง๋๏๓Q‚,๊Oe่ผบŒŸชt้;i%Qา!๚Oใ฿ะE(\‰Vวี๘ฬ;oYmิ๕7\ะ7Bž0Ÿ3เบก๕W๔E’“ั่ๆ'ไไE็„@๗๘กบj€ ฉฒต.˜ฟ>P]๕Ci]ศ–-!uืฦฎ#ึ‘ุRM#ูo๚ํ็>™ ฉj[œquqาหซœ'ื3‚ษ|$x2 ชXt‘จิFคKB`ล๐คโ:A6๔=ฒ*%เุ่ถึ‡BP๐๕b†S 0็… \า,'ญoDr๗‘C๛qึ็m Y๒1u๐แ ท7vGฌE›ิM[7ตn$?VQ•U<€จช„*?๖ฆ๒ฏคฝพแt&ufKGฃ$1(ฑQาฑe&uM™™ฆWไ-ซ`–ๅญ ลึำ•ฃ>มA`ู:ฉ[๕‹‹"วXัD๕มžฐL;š6’6šซรZฦ้M๙ร}ฎดต๘๓”๕ง‹3:I๒€%ฯ,VlUๅ|Z็ใŒซ็n,>ภ%ำR‚ˆณ๘€ญ_ิ‘ู'FeŸ๚3/฿ฏฬทX๒r๎ฟ_]oๅ:ต/'ุ\ใ‡ุYฃCีg&ฝผ.Ÿตœ๎kk่่ศ!„หูแภPj\–ู@ข–๖1๓ร๏>ศฐm5บ๋m…ึE#ฮู}๐ส๊Qา|`V\‹๋†GN ?90prไqhพ,ผ}อ€Z(ำb+Žี…ฉ)ฉO๋='ฮ๔†…4nศฟkน=E%RQ๓๔ซณ๖ซŠแเ฿eฐ๓;IฝูะUตส‚KƒƒKฑeUW•ม\O:๓3J3ฉx<)*i๏ง˜L๐ ฬJ.วญ"VฑN‘0๑ีไญIM˜8!™uผt่‹ือ }u_บบช"<†MษมMr๚*บJว,H'ฅGRา4ีšดท^œฃœqึMœr`<2Ggพ8์rฉ˜จ๑๗ะ6qๅณ`yZ\x'xR‘๘๙)†๊ืlvืฆM"๊|-7uEqBญสV]2ฬช2ฺW\.ณศัศ}กท DุnšมNํ=๗dบ„ืฯุณ๗๎cYซ๗ฐYR—.{Pก+\๑^นา๘Šิn“ Qซษtตซใ๎ฎ›‘ถN์๎:ิี}5xฤ|ฐฺFq9ŸiๆทnยFจ†5:'ด•ื%แาp}”Cภ7ผ–Cคษแ"P[ มถุKL="ปรช™EYŠ๕5่|˜iฅamA™Wง‹Dl^ไ9<ไม$”'šูืz6-่JณยFb–‚šึGพ’A<๚5˜ฟ:›|ณบIAQš^ฆ•์†š˜‘ด&ผdส็}ฏ3€ึXAY z“gีฮ ด๙โ†ถย”NPŠƒหฏ๋.Šw่Ehฐ!Cฺ–/๐\aาpœ9ชžฦYHธ9นrว๛#l“zkะณ`สล8ฒaฃQ75ฺ`*T‚ศ f‚fw๓Vส`lนmส2qDภŽŒ8Lแร`œ๏(X–5ŽไŸ5$ุd'ฯนโิฎH–`ศธฟเ~M ฟR.uh#pIกซฆจ6sผ ผfjE>ˆธ๗4y็ฬเณ0wูF/ƒะm6๐น่eL่iงแดŽIi›ๅaT(ษ†ฏ่฿_,{๗[–€Rอศz!l•,†>@ฌ ๖xถZ๗,B๛๓,๊ซiYv,l๙S^๙Msฝ๕1T?]uึลต ะOƒกฉz๋ญณ:ช MืK{๕ก๑z่กถ-ใuฬ;๊gผญฮlm ุ…]ตฟtํ๕ึK0ฏv6ฐ๐ฺ:ท*<เๆ๕›‘…ื'ชhDฯ(xธ:L๏ถ๔ๆฑ่๕xตo๎Sอํำ=็ีcไว^^ำZoneMinder-1.32.2/web/fonts/MaterialIcons-Regular.ttf0000644000000000000000000037226413365153155021152 0ustar rootroot€pGDEFS‹$GPOSเ๏œ‹,6GSUBไาฉQ‹diPOS/2 s"cx`cmap๑๐1เ xcvt D|gasp‹glyfD}์rHheadฺ‹f6hhea4$hmtxjๆiFุ loca;)–…€œmaxp'แX name5ไ‰dzpost†2Šเ ะOฟ"ˆ_<๕ าฃสาฃออฐ@.LfGLf๕„€0 +++@+@@U@U5+@@Ukk@+++++€++ซ++++@+@@UU€€+€+++@+k•@@+@"UUUUUU+++U++,@++@+@Uw@kU+@++@U++k@@+k+++3@+[kk++@U+>+@Uk@++@+k++@@+@kU=@+++!k++@@@@+@k++@@+++++++++•••••k@U@+U@+++@kU++3+ +f+•‡@@@@@@@@@@@@@@@•+U@@€ 5+Uk@@kUk€@+U+@+@w+@kU@UUUUWUK+U5+k++kk+++++€+@@+€ซท€@€++k@Ukk@@@UkkU@+U@+++@k€+@55+k€@+++kk+@@+@@@+@@@@@@@@kU+@@+U+U+จ@@+,1@+•@@U+@*@+@@@+@@@@@@+++€k+ซท@++U+@U+++@WU@+++@+Y@%++@@@+L++@@+U@%UUU€+@++++@+@+@@U@U@@U+€@@@+++:+@@@`kk+:@@U>kk@k@@U@+Uk+UU•+•U+Iซทk€€kk@UีVžžUkUU€wk@+++@@@@++@@@@@UU++U=@k+@+* +@@+U+€UU@U+ี++U++@+@++UUU+U@+UUU@+@U+++++@@+++++@+@@+++#+++@@@@@@@UUkkU++U++@kU@I @@++++++UU@@k+++++€€U++U@@+@@UUU++U@@+++@kU@ @+++@+k+@-@k€k@UZk++4+@U+@k+U@@++@+kk@+U+U+UUUUU++++++k++@@@+5'€k@+Z+>kek+@++@@U+ซ@U@+@+Uk+?+@++UU+U+++++, , ๐x@89_zเเเ!เ$เ,เ1เ9เSเqเผเฟเฤเๅแmแ•แœแรแศแะแแโโdโฤโษโฬใใใ8ใเใ๎ไไ.ไ<ๅ6ๅrๅๆ ๆEๆลๆๆ฿ๆแ็้่่่่:่Q่s่ถ่ฮ้้้้+๋L0_aเเเเ#เ(เ.เ3เ;เUเฏเพเรเฦแEแแœแฃแศแอแุแเโ&โผโฦโฬใใ ใใใโใ๑ไไ0ๅ-ๅ9ๅรๆๆ#ๆฤๆๆ฿ๆแ็้็๎่ ่่4่M่S่u่ธ่ะ้้้๋;ำฎญ (     อฬษศiGA;73,(ๅŽ‹QPO๋๊่็ๆ๖๔คtr๔ิะวลง•”“’‘Ž เ<09__ azเเ(เเ,เเ!-เ#เ$4เ(เ,6เ.เ1;เ3เ9?เ;เSFเUเq_เฏเผ|เพเฟŠเรเฤŒเฦเๅŽแEแmฎแแ•ืแœแœแฃแรแศแศแอแะแุแแเแโโ&โd โผโฤJโฦโษSโฬโฬWใใXใ ใZใใ8iใใเˆใโใ๎ฬใ๑ไูไไ.ไ0ไ<ๅ-ๅ6#ๅ9ๅr-ๅรๅgๆๆ ‚ๆ#ๆE•ๆฤๆลธๆๆบๆ฿ๆ฿ปๆแๆแผ็้็้ฝ็๎่พ่ ่า่่ึ่4่:่M่Qโ่S่s็่u่ถ่ธ่ฮJ่ะ้a้้’้้—้้+Ÿ๋;๋Lบฬ  !"#$%&'D)))19AIQYaiqy‰‘™กฉฑนมษัูแ้๑๙ !)19AIQn•ญไ Uจภ๊ 3„ส๙$`žล6qขห๛#/FhŽล๐&eƒชห๐M[Œู  ( P f v ด E s ž ภ !  F ฉ ฿0{šฬ5c‡ม๏6Yšผ฿)Hv™๏M`{‘คม์4qพ4Y„าMuฃฝเ/‘ฺ`˜ป"R|ณฺY“ :tŸส FZƒขฬ.Wq ๑+Jl…™ช7kตั๕+Il—  3 Q m › ฮ!!4!n!!ส!"2"Y"’"ฟ"ไ##6#V#v#ถ#ฺ$ $8$q$ž$ั$๘%%L%‰%น%๘&4&e&ฏ&๙'*'P']'s'ถ'((b(ฉ) )N)x)ต)ย))**+*:*‰*ฌ*ึ++P+ +ุ,,7,--<- -๖.^..๔/*/‘/๗00B0h00บ011;1Y1‚1ฌ1ม1่2#2_2Œ2ฅ2ภ223!3A3X3„3ฒ3า44>4r4–4ฑ4ฬ4๋555b5y5š5ฑ566&6v6œ6ว6ู6๕737E7w7˜7ร7ํ88Z8”8พ8ี8๋9 9.9a9Œ9ฦ: :0:Y:„:ส:๑;;<;e;ฝ;ฮ;฿;๏;<<*<†<ž<น<ไ= =&=[=‚=อ=๎>>C>s>ฒ>?(?K?‡?ฉ?่@1@U@~@ฌ@ิ@AAEAcAฑA๐B'BPB}B‘BฌCBCีDณEงEธEาE์FFCFgFšFศF๏G/GeGคGH%HcHฌHI*IRIƒIญI่J J-JRJtJ•JนJ์KK:K\K€K™KฌKหLLLILgLฌL๋M$MmMผM๕N2NyNวOOaOฐP P0PnPญP฿P๒Q#QƒQไR'RIRgRxRยS$SƒSแT1TwTดTUUyUUึV VV[VฌVพV๗WWDWuWฏWำXX5XxXŸXธXีY YY-YzYžYปY๎ZZRZvZกZื[[c[[ษ[\7\p\™\ส] ]8]g]ฃ]ำ]^{^๚_,_w_ฝ_๋` `f``ต`ึaaSaƒaงbblb“bศcc]cŸcแddIdndšd๖e=e€eฯff=f|fฐfีf๙ggHgjgฒg์h h0h hฯhiRii฿jj,jrj‘jสk k4kSk{kงkฬll+lZl‡lฝl๛mBmwmmูm๋nn>nln™nฮn๒oohooศo๚p p?ppp™pๅqqgqฎq์r4rlrrrคrฐrลr๊r๛s ss7sHsYs}sขsปsใt t1tMtht}t“tฉtพtีt์u,ueuuธuแv$vYv—vอv๛wwZw—wโxxZxฌx๋y$yNytyžyฤy๓z7zrzžz{{W{{{เ||4|T|…|ฒ|ใ})}g}ข}ึ~~,~L~Š~ฑ~ํ3jฉุ๏€€&€P€z€ค€ทh ๅ‚*‚f‚ข‚ฬ‚ƒ=ƒzƒฑƒ็„,„d„ภ„……:…X…„…ม…††G†ˆ†ภ‡‡M‡‡‡ญ‡ั‡๎ˆˆ-ˆNˆuˆโ‰‰)‰`‰‘‰นŠ Š>ŠwŠษŠ‹i‹Ž‹ม‹ํŒ-ŒrŒฌŒๅU„ฎั่ŽŽUŽŽฎŽมŽ1Lv“ด฿%6V†น‘‘X‘z‘พ‘ไ’’M’ซ’๔“ “"“V“Š“บ“่“”!”6”p”Ž”ถ”ใ•••=•ก•ฬ•๖–0–m–ญ–๏——A—n—™—ฦ˜˜*˜m˜ง™™H™|™ฐ™ไšš,šIšpšŸšฦš๑›3›’›ช›ุœœ'œQœณ+ZŽภžž.žjžสŸ*ŸTŸฌŸๆ # Y ™ ี ก7กmกฏก๋ขข0ขTขrขงขภขูขฃ,ฃQฃถฃ๖ค$คRค˜คยค๓ฅ!ฅlฅฆฅพฅฯฅ็ฅฆฆ<ฆlฆ†ฆŸฆนฆแงง+งRงoง‚งฦง๒จEจคจาฉฉ=ฉoฉ‚ฉปฉ๖ชชNช~ชนชใซ ซ5ซTซ„ซฆซวซ๗ฌ;ญญ&ญbญ‹ญฎฎfฎ€ฎบฎ๋ฏฏZฏzฏฟฏ฿ฐNฐqฐฆฐ๓ฑ2ฑ|ฑฎฑศฒฒ?ฒธฒณ9ณpณฃณได+ดiด›ดืต+ตฆต๊ถถEถmถททมทๆธ9ธˆธดน$™U.ฑ/<ฒํ2ฑ<ฒํ2ฑ/<ฒํ2ฒ<ฒํ233'3#ˆwffUซ31111111111111111111111111111111111111++ีี%5#5#2"&4***Cฐ}}ฐ}๋€€V++@}ฐ}}ฐ++ีี6264&"2"&473#3#บŒeeŒeSฐ}}ฐ}ภ****UeŒeeŒ}ฐ}}ฐ€++@๋ี %5#5#***ึ๋๋ีVVU++@•k@ภี %%5#5##335!57546754623"&U@*@@*~-€-@11@ฝT"๊+@@+@@Q--|3Q  Q3ี++ีี2"&4264&"62"&4๗  P88P8ฐ}}ฐ}  i8P88P}ฐ}}ฐ@@ภภ*26462"6"&462'32"&5475'2654&'#462"€  ๔  ‰Ppp pM‘t!W|WJ6*  ๗    ซp ppP`9‘s)5>WW>8T)฿  @Uภซ/?54&##"332655##53#54&##"332655##5372#!"&5463€ @ @ ++u @ @ ++สึ V  @  V  @ –UUซซ 3#!5333UVVV*V@๋ซซVช@@ภภ 5#35#535#572#!"&5463@€€UUUชึ@+ึ++*+€ึ*U€ห€#ถถภถ€€€€5€ซ€7#'7๕ถภถถ€€€++ีี 3#'7##5%'53`uu@`@@€ @@u@@€@€@`@@u@@€ @@u@+ภๆ5462"'&472653#"'&'&'&'&5462#4&"๕ ,,r88EEๆ+2#))W~V+=Z>$( *,,ฝ8 8EยEo#27%)*?VV?->>- "& @Uภซ#3%5354&##"33353265##5#35372#!"&54635+ @   • + +สึเ@@ V V €55€++๋Uซ๋%55"&5472'654&#'7UUFeK5FeK5UU€@UV@eF2)!5K+eF2)!5K@UVk@•ี3#5&&5326"&55462q$K5*5K$C\CW4&&4&6QFFQ6/==&€&&€k@•ี(3#5&&5326'326574&""&55462q$K5*5K$C\C‹ 44&&4&6QFFQ6/==ฑ„  „ อ&€&&€@@ภี!''#5&&53327'#"&55''5462'65[eY!*5K$C.#&€€&4&U ภ›YFFQ6/= #&€“&&€%!+Uีซ3#!"&546333'33'33€Uช*@**+@+++@ซีVVVVV++ีี $5#5##33572#!"&5463!!"&5•U+UU+kV+ี+UU+UUภUี*+++ีี $5#5#75#72#!"&5463!!"&5•ี€€ีี๋V+ีk**ซ++U++ภUี*+++ีี&!!"&55#&#"265572#!"&5463U+ีUU ,k€ี*+*u ,vj!๋฿%5#5#%'''7'7'777***4M(II(M44M(II(M๋€€V++k;ODCO;>ซ1qดขe1€€1>>++ีี 5!5#5!572#!463€ซซ+ีUU++€++k++•U€@•ภk !!5!%5!•+ีU+*k+ซ++V**++ีี 5!5!5!%'!"&5463!2€UUีVU++@++@++ึ€U+ี#'%54&"6"26472#!"&54635!!5kIDI(({ชVช•  ((WU++++@@ภภ &26%2#"&5463327675#'3##'#535#53#7#5ซ –ี K  //^/ $(@+@@++@+Vต K ี– ($ /\1/  เ+@+@@@+kk Uซ๋'/7?GO2"&42"&462"&42"&4&2"&46"&4622"&462"&462"&42"&4๏""""š""""f""ผ""ฤ""""""š""๋""f""""f""""ฤ""ฤ""š""š""š""+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ++ีี ##463!22'#"&55!5k ีU  U U๋  V+ @ ภU +ภk@•ภ %3'353'##5#U@UU@*•U@*@•UU–•U––@ ภะ'7'5''#"'&&7'71Oy&›fLํ:/W$M lDG. EAR DฒgD.W>0ENk+•ี264&"&2.54๊,, |W,+4'  ,,ซW>PF=EAR>++ีี 5!5!5!2#!463€+ีUU++@++@++U€++ีี 2#!463ซีUีU€++ีี%!72#!463ซช++ีUซี+*U€3รภ '##"&55'%'732Nu( ึ8b๒2–ญŒ)๏8๙๒2@@ภภ7632#"&5463320]/ $( –ี K  ]0/  K ี– ($ +5ีี"2B'#"&57'&&547'&&547'"'632'654&'654&#"'632Fe " *#!.'19,,ี+%2=X} e1#2# #5Kห›ก"1%;#&.6.O%c:J9-}X=2')Fe# #2# K5@ภ/%&4737'7'54&""26472#!"&5463}# *,,* uXPXš4&&4&๊Vี,*+!4,4!+*U%%&4&&4fึ*@ภ #)5#54&""26472#!"&5463'57ีชXPXš4&&4&๊V•@@@@€€€%%&4&&4fึ*k**++Cี *&'77#5'7"'&'&55&""''&476 ‰JLn*ุLLU5 .h. 5i"/JLˆjj[KL5BB5d[+ฅ๋#%5#72##"&55463&2&"'62&"@€}  z  #ภE8ž8:,~,ZUซซี ฯ  ฯ ภE88=,,k๋•!!%463!2#!"&5•ึ*€€kึึึk•๋%#2##"&5463kึึึk*ึ€€k๋•!!%463!2#!"&5•ึ*€€kึึึk•๋%#2##"&5463kึึึk*ึ€€+kีซ!#"&554&"3'354622655#€U@2F2"@UU@2F2"@ซV•#22#••UU•#33#••++ีี 5##5##5#%2#!463k++*++ีU++++++ภU€•€#$264&"264&"$2#!"&4623&54l>++>,๊>,,>+/bDD1๊1DDbE`ภ,>++>,,>++>”DbEEbDD1+ +1€๋€6264&"73##5##"&4632„""ฃ+U] B*5KK5*Bี""ภ๋T2##"&5533##5463264&"7#'##"&7'&'''4775'&7763677633276•ี+ีี+&""|+ +๋€@+V+@๊""%  $%  $@๋ภ #7##!2#!"&5463ี*UU*V๋€€€UUUA,ิVึ*5๋  &%5&#"6322#"'&#"&#""#"&56326ภ!*A44A''L*)=A4+J60+KJ++u๖ ๕  ษ 9 +Uีซ7!5'2#!"&5463ซชVซซVชkีjjี+Uซ%7'633!53"&55463!2VVi+U–UUVหPP.n:z++ีี!%767'''!53"&5547''7'"'!23#•1" nฅ:}U!ฝv"Vp;ภ( "ไ[:+ึ !๊ v O-o+@ภk 77#53#5@ขwbซ+•Mขw+ซb•UUกก2#44#4&#462#"&UX|=Y>‰ร<Ÿq()|X>Yตร‰qŸแ(kk•• %##5#53533•€*€€*€๋€€*€€@@ภภ %5#5##33572#!"&5463kV*VV*€ึ๋*VV*VVีึ*++ีี %5#5##335&2"&4kV*VV*mฐ}}ฐ}๋*VV*VV๊}ฐ}}ฐ++ีี6264&"2"&473##5#535บŒeeŒeSฐ}}ฐ}๊VV*VVUeŒeeŒ}ฐ}}ฐV*VV*V@@ภภ !'!7#5##%#!"&547763!2m&uJVJ+ ึ   •เu++ ๖  $ @ภ %'7''72#!"''763•LLLMMMML^ภssณMMMMMMMM+ึญญ++ีี%2654''7&#"62"&4Fe$๐-o$๐-76'&( ภ% #  09*)54>!(<7 ;  #2  t%&&  ๙$ ('$$<7"5E>'!H  I 0$$"@@ภภ %5!32652#!"&5463•ีV&4&Uีภีี&&ึ*+•ีk#2##53264&##553$33#"&4633#"k,>>,VV''Vjช'VV,>>,VVk?X?)'6')€**06')?X?)+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ!ซีk7#7&#"'6632‰LภN0>8Y2uIUMภM(A4DVk๋•%!5!•ึ*๋*++ีี %5#62"&4kึฐ}}ฐ}๋**๊}ฐ}}ฐ++ีี6264&"2"&43#บŒeeŒeSฐ}}ฐ}jึึUeŒeeŒ}ฐ}}ฐC*@Uภ•&#'7ีhqMž••@[mW••U•&#'7'7hqMž••€UU••@[mW••@UU@••@@ภภ %5#264&"#'57* lpp pp๋€€\ p pp p@@ภภ 5#264&"#!"&5463@ี{4&&4&ซUึ@UUี&4&&4/U*@@ภภ  $(,16:>BFK35535353753'5353'5353#553"&532#'#55353'53'463ภ€ซึ++++*++++++ี*U+U++*+€*ซ+*+€++@€€ซึึ++ซ++U++ซ+++ซ**ซ++€++ี++UU+++€++++ซ**ช++@๋ภ75%%5+@ภภ@•++•ภ@€ภ€ 75!%!!53@€€€๋**•+ี++kk•ซ 3#73#'!!(P],e e,ส*ึ€k&/๋๋/Z*+ซ฿k2&&#"#56 It2Y8>0NภL?UVD4A(MภM7++ีี%3#3732#!"&54637T-m(m-xoช,,u๊@ ชV๕vv@@ภภ '3535!32652#!"&5463UUU*VjีV&4&Uี+VV@@kีี&&ึ*@@ภภ !'!33537#!"&547763!2m&uJVJA ึ   •vu++ั ๖  # ++ีภ #77'355232#!"&5546335463๋UU@@VVชVuVU@@ **+*๋๋*k๋•'2!54&'54632#!"&55462!54€/"€"*•. ,, .jkk@@+Uีซ!537353##"&%3#53#3#+@Uซี€€€••UU•**๋ีี‘+€*€++Uี•64633#"3355#"73#53#53#+Q9KK(88( @@ 9ฏชชชชชชาrQ*8P8*@@+++ + *++ีุ6264&"62"&47'5''7'7ย|WW|WE pp pหUeCbbHbUX|WW|qžqqžp2<€cR RR!S ++ีฺ6264&"62"&47'5''7'7ย|WW|WE pp pหUdBcbHcUX|WW|p pp q3>€bQ QQ T ++ีี '5264&"2"&4 `p1ŒeeŒeSฐ}}ฐ}kp9D€๊eŒeeŒ}ฐ}}ฐ++ีุ #3##5#535264&"62"&4%'7'7@@*@@)|WW|WE pp p•bหbb@@+@@+@๋X|WW|qžqqžL!S R R++ภี7''575577''5462@P{+KJ*ชkซDงฐzO  u5*Pk*งN  ++ภี''575575462ู็ซ+KJ*ชชซ@•5u  u5*ku  uk•+kี%5#5#2##"&5463353***c  œ  #VีkkU+++ น  G **•+kี77#5372##"&5463353๋U+U+c  œ  #VU v เ น  G **•+kี2##"&5463353N  œ  #Vซ น  G **•+kี2##"&5463353N  œ  #Vซ น  G **•+kี,%654&"34623475#2##"&54633531&4&  "(b  œ  #V๑&&  a))* น  G **k+zี%'73#5'7'753=((=\\zbwwbค)QQ)\\yขbwwbข@+ภี ''73#5'7'753'7•++*.((=\\zbwwbk*++++++\)QQ)\\yขbwwbขี+++U+ซี %7'''#5'7''53'7((ข81\bwภ*zA"|()ศ1[ขbw E+kyA#@+ภี!%'73#5'7'753'64'7((<\\zbwwbฬ!V1 ค)QQ)\\yขbwwbขd2z4*X*V1๑๑%3'#3737#'#5'7537371)E*E)D‰FFdGGdFFdGGdฤซภภ*rGGdFFdGGdFFนNN๑๑2"&4264&"%#'#5'75373F22F2 jKKjK+FFdGGdFFdGGdU2F22FฃKjKKj|GGdFFdGGdFF๑๑6264&"#'#5'75373หjKKjK+dGGdFFdGGdF€KjKKjdFFdGGdFFdG๑๑%264&##'#5'753735KK5ซdGGdFFdGGdF€KjKวdFFdGGdFFdG++ีิ%27#"&5467'654&'H,8@lX}oQ6JWSQo8 J6k8!W}XSy@T8>WiyS0'!8TUซ๋ '%53##"&5537'7'7'7%#54632#5k*ึ*@bbCทCCb๊*ึ*k*UU*QbbDbDDb•*UU*Uซ %5#72##"&55463%3!535463!ีUk € ๊ึี+€•––ภ ี ี +๋@@๋+@๋ภ '#57#5#57#5!2###5#"&5463•****๊๊๊€€kชk++U**U++U**ภ+++๋๋'6264&"%3##5&&'#53667532"&4ย|WW|WT,,`C*C`,,`C*C`F22F2kW|WW|S*C`,,`C*C`,,`2F22F๋๋6264&"%3##5&&'#5366753ย|WW|WT,,`C*C`,,`C*C`kW|WW|S*C`,,`C*C`,,`๋๋/%'327'#5&&'#53673#'654&#"'6753[ัW>2๒e,-7*C`,,%S,, W> %*C`Šั)2>W:›,%,,`C*7-d*' >W ,,`๋๋/%'327'#5&&'#53673#'654&#"'6753[ัW>2๒e,-7*C`,,%S,, W> %*C`Šั)2>W:›,%,,`C*7-d*' >W ,,`๋๋6264&"%3##5&&'#5366753ย|WW|WT,,`C*C`,,`C*C`kW|WW|S*C`,,`C*C`,,`@+ภี 3#3%533'3•++U+ี+€*€++VUUVVชชVU++ีี%!ีVช+ช6๘ภ '07'62ดตตDyyเเTUU++ีี,!3#35#"&5475463!2#!"&5463€U*ช@"–ชVช€+ชช0  0ีVช€ชV++ีี"'073#"&5553##52#5#5"&4627!#54633U––€*––*–Vƒ@+@+*–๋–*––––*€––*}€O9U+––*@@ 3#53'#5353cyy]ซNxซซซซyxซซxNซ]ซซk๋•!1354&""&5546354622#5!2#!"&55463ๆ4 $ jึU€+ Š @  @ ึึึึk•๋1%#2##"&5463354&""&5546354622#kึึึQ4 $ k*ึ€€€ภ Š @  @ ๚'?354&""&5546354622#7"&'3%"'&47762'7'7fI , ๕Qd“ Ep ˆ  ‡  4-y๒x/ห š V  V ๕Qˆc;aŒ  ‡   ˆ 4,x๒y/ '77"&'377"'&47762'72#&& Qd“ Dัˆˆž ˆ  ˆ Qd“ D6Qˆc<`ˆˆŸ  ˆ   ˆ Qˆc<`U+ซี 5##5##5#72#!"&57€+++ซUVVVVVV€ช€@๋ภ&%!2#!"&5463"&5467363232#ภ€€€€&!.*j,ิVึ*๋&$+%,++ีี7+ช+ชV++ีี 7#353'53+ชU+***+ช€ึ**Uซซ3รภ '##"&55'%'732Nu( ึ8b๒2–ญŒ)๏8๙๒2++ีี!!ีV€็ีVC็ี๋ '!7'%'fo*…ฝ‡uท +ฝˆf‘ท6๘ภ%62๘yy65UU +ภ#9%7>2&#"54&"32##"&554635462KK๗  $'-.-'$  ,-=ŠV k .ห8^6      8=M  U U 6๘แ '67''632F HGS๘!-+อt-,แ ๆJGg5,[‘ +Uีซ 355!%5#'!!355!U+Uชซ+*ชV*+Uช**@VV–**@Vภ**@VVf&•๋,3###"&5475#"&55&546235#7#35#@U@(@&@+@@+@kV*AA, ,ชVVช*+ี)%54&"32##"&554635462'"6 &ีV k , 6,>Kp p9ซ  U U j>,=cUUUL+2ีภ-52'6654&"&&54'654&"&&54622"&4จฐ}91'/dŽd.'19U#*2F2*#KjK‘""ภ|Y:c%O.FddF/N%c:YY#;%1#22#1%;#5KK ""•€๋'3"&5462"&55326554&"265` DbE3F2,  , 3F2€๕1EE1 #33#เหห เ ๖#33#‡@`ภ(#5&&'332654'&546753#&#" )"@!+/;@d* @!#/4  '..*!-$A)./,-@@ภภ 5#5#'5#5#!!•€€€*€€€+€€€€ช€€ช€€ช€€U€@@ภภ #'+/37;?C7#55!%#5%53'3##553'53'#5##5#5'#5'#5#5'#5#5'#5k+€ซ+U++++ึ+U+++U++*€++*€+ี*++€*++ภ++€++ี**+++€+*++ึ++V**ี++++ซ**V++U++ซ**ซ++++U**@@ภภ #'+/37;?CGKOS5353535373#5335353'5353'53'5353535353353'5353'53'53@+++++€*€++ช*€+++++++ี*ี+++++++++€***€+++++•++ช**ซ++U++++U++++++ซ**V++V**ช++U++U**V++U++++U++U++ซ**ช++ 5!!'762#57บ*P*2EึPึUUช*P*2EึPึ@@ภภ #'+/37;?C%53#53'53753'3#5!53753#5#57#5##5##553'535#553•+€+€*€++++ซ€ี*€+ซ***€+€+*+U+€+++@++++U++ซ++€+ช**ซ++U+++++U++U++++++€++U++ึ++ี++ @@ภภ#'+/37;%53533##5#535533#53'#5##53#5535#553#53•+++ซซซ*ซซU+*+++U+ี+€+U++U+€+•++U++€ซ*ซซ*ซ€++€+U++€++++++ี++ึ++ี++++@@ภภ #'+/37;?C535353'53'3#5353533753'5353753'53'535353@+++*+++++++€+*+€+*++++++*********•++ช**ซ++ซ**ี+++U++++€€ซ**ช++ซ++ซ**U++U++++U++@@ภภ #5#5!!!%#5##57#5ภ+€*ชึ+€€+++*****U++U*ึU€ี****V++@@ภภ #'+/37;?C53'5353753533#'5353'5353'5353#53753'53#5353๋*****++++*++U+€*ี+++++€*ี+*+++€+*+@++U++ช**ช++ซ++€€ซ**V++ซ++ซ++V**ซ++++ซ**ช++++ซ++@@ภภ !53%!!#%535353#53353#53•+€€ซ+U+++ี*€+ี+€+@++€+ซซ**V++U++++++++@@ภภ #'+/37;?C%53535353753!!53'5353'535353'5353'53#5353@+*+ี*++*+€€€U+++ี*ี+++++++€***€+++๋**ซ++++++U++++ช**U++ซ++ซ++U**ซ++U++U++ซ****ซ++@@ภภ #'+/37;?C%53535353'3#5353#3753535353'53353535353@+++++*+++++++ี*€+ี+€+++++*+++€+++๋**ซ++U++U++€+ช**ซ++€€U++++++U++ซ****ซ++U++U++@@ภภ !!3#5!5!%3#@€€UึึU€€€ีึึภ+*+U**ซ++€+@@ภภ !!5!5!5!5!@€€€€€€€€€ภ+U++U**V++U++@@ภภ !!5!%5!'!5!5@€€€€€€ภ+ซ++ซ**€++ซ++@@ภภ !!5!5!5!5!@€€€€€€€ภ+U++U**V++U++•€{ซ%264&##53264&###32 K@  8.-"—†$1ต@ภ@[4"/+2H+@ซ•!#'7#'''#7'€+|"-3<:4y!@4”•@P,$<หyN{”!5!!"&5467%3'#"''&477'7•+" ๅอgŒ u v n3UU / & -fP  u u  n3U=จผ '#"&547''7p89%/5KG+ท:+Gศ8 K5"6GŸ ธK6{ภ 3''3#'##!!อf3*u0†0u‡9ี@@@U@@ภภ 753'53%!!5!%753๋ีีี€€€€€UVี๋**U++€+ซ++ภUช++@@ภภ 753'53%!!53%5!๋ีีี€€€ซี€UU€๋**U++€+++ภUUk++€€€ซ 3#3#537#ีซ,5k+UK5ซซ++ซซ5€U€ซ #3!57'5€•jj•‹‹ซ@kk@+€€+@@ภภ %5##5##5#2#!"&5463k++*++ึ•VVึึ––+ึ*++ีี 5!5!5!2'!"&5463€+UีU++@++@++€UU+ซี3''3#!"&546vv•ซ€@u €V++ีี %$"&'3&"&462"&462264&"2"&4%J; ฺ žƒฑŒeeŒeSฐ}}ฐ}‹)!!aำeŒeeŒ}ฐ}}ฐ@@ภ๋%5!332#!"&54633533#5•ึ๊+ึ+ชkk๊๊€+ึ*++ภkk+•ีk#2##53264&##553$33#"&4633#"k,>>,VV''Vjช'VV,>>,VVk?X?)'6')€**06')?X?)@@ภภ7!'#!"&5463!2ตJ*`Jีึ*เ`€`5*wM‰ต 7#'75'7 ``K€t€IIU``ˆ€sw๘HH++ีี '!"&5463!2ีUีVซ€U@@ภภ '7627#บ'P'2€์P์Pj'P'2เ์P์kU•ซ 77##5'!!k••U€U*ึี––€€ึ+Uภซ@3!53!€+ช+@€€U@€ภภ873254&'"&&##5!##"'&'&574#"#4&5&547632ศ>1 อ€SC$'ฎ3$ b "15่6$++=2x-  %)U@ซภ 7!!%'353UVชUU@*k+ซVVีีUซ๋ !!%'3537##5UVชUU@*jUU@**ชUUVVึUUVVU@ซภ !!7##5UVชVUU@*ภ+€VVีีU@ภ•2##'73264&#!5%!553k#22#+@@0ๅVช€2F2+@@+"*€**ึ**W@ฉภ"'#5&&'3327'&5'"'6753#&r7/!@!,/<%KSIด@ #/ฉศ0..*!-J;I!./,-Uภซ@75!5!5UVชภ++€++๋๋ "&:73'##73#735#5#'535#5##35#35#3#5##535#53353ไ8%K#IH"`++++***ึ***++€++€ึ€++€ึ€๐Rm*ภภ๊+€+++*ึ**ึ*+++€++Uึ€++€ึ€++K+ตี 7%773#5!#5j--ร-s**k@€t-..-€@ภkk@jj+หี52#"'#"'##"&463236236 $ > H > $$ > H > 5, , Uภซ@73#5!!UึึVช๋+€+5kหซ##5#5'!###ห@@@ึk@k@@••@k@++ีี"*%654'&546323&'5##"'#32"&4CZ8-*;9%Y95*C9vฐ}}ฐ}~ 6=(9**#8 ! (9*€}ฐ}}ฐkk•ซ!###k*u@uซ@+€ีk%646332##"&4633#"33264&##"33#"+D1เ#22#ต  ข ทเ,,หห1ฤbE3F2, +",>++Uซ#!"&54676632):?,๋5KB0L-:X*=*,?K51I'0I++ีี%264&##4&#"'"32"&4` 2#-&&ฐ}}ฐ}ซ,#3%&4&*}ฐ}}ฐUซ77''%#!"&54676632ีo,):?,๋5KB0L-:X•n,J=*,?K51I'0IUซ%#5##7#!"&54676632k@V@k):?,๋5KB0L-:X๋UUkช=*,?K51I'0I+ซ+#"337'#"&5467'654&##54&#"'632ฅ%#22#ะ๐e+๚5KH3"):-!& D1 '/:X+3F2›*K54J+=*7 && 1D IUซ&%264&##54&#"#"3%#!"&54676632•&& D1(> #22#):?,๋5KB0L-:X€&4& 1D0%3F2ช=*,?K51I'0IUซ%3'337#!"&54676632+@kk@Vr):?,๋5KB0L-:X๋jjV•=*,?K51I'0IkU•ภ 7!!%'353k*ึ*••U€€+๋••€€kU•ภ 7!!75#7#k*ึUU••U€+V€••€+Uีซ32#!"&5463ี+ซชซ+ี+Uีซ%5!2#!"&54633ซชVช€+€ีีี++Uีซ!%54&"6"26472#!"&54633•;4;f""@ช€+•ซ""Zี++Uีซ %5#5##33572#!"&54633•@*@@*Vช€+ี+@@+@@ซี+@๋ภ'2#4&#2#4&#2#2##53!#5463aŠ+qO>X+?,&@ซ••€++ŠaPp+W>,?+&€ึ+*@@@๋ภ!).2##53!#54632#4&#%#&&'52#4&#2#ภ••€++aŠ+qO€x_?V>X+?,&@ภึ+*@@•ŠaPpkึ?`#–W>,?+&Uซ!53!53"&55463!2UVUUV€ีี++ีี+๋ี%5!2###57#"&5463ภ€€•*ช*•ีึึ@@+๋ี%!2##3#535#"&5463ภ€€•*ช*•ซ*+**++@ีภ /3#'3#73#3#!%#3#3##!"&5463!23UU€kk€UU€kkี€*****ี+*€ึkk@@V**ึี+*++***€€๋%5#2##"&546353Uชชชชภีี*ี+*++++ีี 3#'7##5%'53`uu@`@@€ @@u@@€@€@`@@u@@€ @@u@Uภ๋2##5354&"3#"&554ฐ p&@UW|WU@&๋qO–&ซ+>WW>+ซ&–O@ภ๋2##535#5354&"3#"&554ฐ p&€•UUW|WU@&๋qOึ&+ซ+>WW>+ซ&–O +kี• #'+;5#5#'5#5#5#'5#5#735'3535'3572#!"&55463•*******ช***@******–ช+**@**@**@**V++–**@*****@**@**@**@ึึ€ป€Y7'žbb€€Ybb€€ซ‹I‹%'7I€€bฉ€€bท…U…77'7ทbb€€ฃbb€€€ท€U7'7'ž€€bท€€b@€ภ€!'7ภาL€€L*M€€M€€€‰ 75!''7€€b€€€++อb€€ +ีภ "&*.>%'375#5#'5#5#5#'5#5#735'3535'3572#!"&55463Uช@*******ช***@******–ชV๊++@++@++@++U++•++@+++++@++@++@++@ีี+€ภ€ 3!'7!•+ผM€€Mk€M€€M€ี€ 3#'7'7!5!ซ**ด€€Mั/€โ€€M*k+•ภ3#5&&5326"&55462q$K5*5K$C\CW4&&4&6QFFQ6/>>&€&&€Uซ!53!53"&55463!2UVUUV€ีี++ีีUภ%5!5#!3!53!ซชึV++ชภีี@++@Uภ #6264&"!53#!"&53"&55463!2๗  –VUVUVk  ๊๊๋๊๊Uภ!53!535"&55463!2#UVUUV•ีี๋++ีี@@ภภ37;%5#%#3####5##5#"&55#535#5354633533533235#7#5kึ++++++*++++++++*+++ซ*U€•ึึ€*++++++++*+++++++U**U€€Uซ้ #465!"&#๋–WWVeŒeภ?W–้ฉA`฿UUFeeo`Ak•๋%#5#2##"&5463pเ›V€&&ช&&€+ี@ซ&ช&&V&k€๋ %#264&"2##"&5463UภSvซ€+ีUญ – j Uซ %5#72##"&55463%3!535463!ีUk € ๊ึี+€•––ภ ี ี +๋@@๋+#"&2##'35#'5463%3'!53547'!'!๋ @.U+ ๊ๅ๏Q+"2†+ 'ว็+DU ี @–o+Y %ๅHQิ"2@๋ 'B+@@ภภ )3%5##5##5#%2#!"&554633537&#"'632&#"'632@+ * +ึี+1! ()1)10)-=>-€++++++kUUUU† $$--@Uภ•%5##5#%#!"&55463!%7•ี+*; ึ ิ•++++‡ uUm(@ภ๋ &&55667#5ภnRRnภ;R••๋V€Y““Y€•ฟiCผBzU+ซี %%5#'5#5#'5#5##5##!"&5732k++***++ึ+€+€ซภUU+**€UUUUUU****@ช€k•๋%#2##"&5463kึึึk*ึ€€k+•ี)2"&4264&""3264&72##"&5463ๆ4&&4&X??X?kZึ&4&&4…?X??X""*ชVUซ๋!13#"&5462"264&"6"326472##"&5463€ีี  ,, F22F3g"/ณ•ซ+Uถ, , 2F33F๎$$Dอ3U๋ซ%!2#!"&5463•ึU€€+@ภ%!5#2#!"&5463›สฦVซ&&&&kUซ@ี&€&&€&+ภ %!264&"2#!"&5463•ภ“ถีkUซVุj–๋๋ "&53"&4632#5462E`F๋0EE00EE0E`F0EE0E`F๋E`F๋0EE0@๋ภ%!2###5#"&5463ภ€€kชk•+++Uซ264&"#'&4773€KjKKjเAชAAช5jKKjK.ค4zz2จ2zz@@ภภ%3#5'#5375&&5462kUkUUkUV&4&ซkAZZAkUD && D+ภภ@ %53!53!53'!!Ukkjj•kภ++++++€+U๋ซ)7%5#72##"&55463264&"7#5&5475'3#"&5463!ภUj€ wJU€UU€€ซซี ีี สb&&&&€++€๋€+264&"264&"'5#5##335%2#!"&55463’B@@+@@+€R*@@*@@•ชช++ีี $5#5##33572#!"&5463!!"&5•U+UU+kV+ี+UU+UUภUี*+++ีี"&462264&"2"&4@&4&&4`ŒeeŒeSฐ}}ฐ}4&&4&๋eŒeeŒ}ฐ}}ฐ@ภี%7''2##'#"&5463(XX((XX(•U@@Uํ((XX((X@ี@@+k@ซซ 3#'##33x–x*ภ€ี*•k€@•ภ3###"&4632•U6%(88(ภ@๋$18P8++ีี'/7?GOW_go$2"&42#"54264&"2"&42#"542#"54'"54322"&4'"54322#"542#"54'2#"542"&462"&4"   fŒeeŒeSฐ}}ฐ}@ 5   @ K J 6 A    ๋  > KeŒeeŒ}ฐ}}ฐ# U €  , € @ • J  b  @@ภภ%)19AIMU]e$"&462&"&462&"&462"432'"5432%!!"5432"&462&"&462&"&4625!"&462"&462"&462      @ ห€€+ ฌ  ]K€๗    ]•  I  I  WV u+๕  >C๘++  a  l55หห !'/TZbjrz€†Œ˜ 62"52"&42#"4&2"52"&4$2"52"&47'"&4632'#"&5467'"&4632#"4"&462"&462"&462&"&462"542'"4323"432&&'5462#6"&4625B  j –B  J้  ‹[P  <  <  ฅ i      ท  เ๖ K     เ @  >๊ @   @  ฅQ  <  <  ๔  b  b  Ÿ  W Šช  `  55หห%-3;CKS[ciou{ƒ‰‘—Ÿงฏ2"&42"&42"&462"&42#"462"&462"5&2"&42"&42"&462"&4&2"&46"&462'"4322#"4&2"57"432"&462"542$2"&42"562"&42"&462"&4?  v    >        ฃ)   –๖   ‹ฌ  6B      KCM  ยํJ  W ภ  I  Ÿ  b  ‰  >–• เJ  W j  T   Ÿ  b  ++ีี6462"+}ฐ}}ฐจฐ}}ฐ}k+ซี 2#"'664&'6ีY}}Y:019910ี}ฐ}ctc€+•ี 2#"'664&'6ภX}}X"BSSBี}ฐ} qŽq ๑๑ %264&#"7#'#5'753735KK5!))!ลFFdGGdFFdGGd€KjK ?L? วGGdFFdGGdFF๑๑6264&"#'#5'75373หjKKjK+dGGdFFdGGdF€KjKKjdFFdGGdFFdG๑๑%264&##'#5'753735KK5ซdGGdFFdGGdF€KjKวdFFdGGdFFdG๑๑2"&4264&"%#'#5'75373F22F2 jKKjK+FFdGGdFFdGGdU2F22FฃKjKKj|GGdFFdGGdFF@@ภภ #!"&5577'''5463!2€@ึ@UV•@UVU@* @aŒ@VVŒ@VVVAa+@ภภ '7622#"'2654บฟ;ฟ4&2#4!ฟ;ฟๅ&#2+++ีี %7667#"'3&&7#&54''7#7'632าNN;LทฯO.C l ฆ8f›ฯO.C๔N;L0ˆ#‡0•‡GปS(S=ฐ ‡GO‡0+Uีี#6264&"332#!"&54633462"ิX??X?+€'DชD#(8((8•?X??X*ว8((8(k•'+462552##5#535#"&5463"&4623#•IDI–@@jj@|""jjีเเ+ึ@@+*+*ซ""ๆ*k• $264&"72##5#535#"&54633#"}–@@jj@–jj€""€ึ@@+*+*U*++ี๋ 35#5#'5#5#'5#5#3###"&5463354633232ซ++++***++++Vชชซ U @++ภ++ภ++ภ++ภ++ภ++ภ@ @@ภภ#+%53##52#5#5#546333#"&5562"&4•+UU+Uี+UUUUF22F2kUU+UUU++UU+ีU+U•2F22F@@ภภ!*36264&"62"&453##52#5#5#546333#"&55๏""F22F2๊+UU+Uี+UUUUี""f2F22FธUU+UUU++UU+ีU+U++ีี 3!!"&57!'#!"&5463!2+*+ีภ@V?ฟ€ี*ซUjO@@ภภ4264&"&264&"264&"264&"72##"#"&46h-W-•Oq?,%PppChฎdG,>  p p@@ษภ77'%'#57'7762”ฌ)ฌOC)ฟeพ)Ckฌ)ฌ๔C)พeฟ)C@ภ๋ 2##5#5553#5#"&5463•jjjVjj++jภึภ€+ภ€€@+*+*++ีี6264&"2"&473##5#535บŒeeŒeSฐ}}ฐ}๊VV*VVUeŒeeŒ}ฐ}}ฐV*VV*V@ภ'$264&"62"&4&&4673##5#535|WW|WE pp pU.'8HH8'@@*@@kW|WW|p pp $XI.dzd. @*@@*@@€ภ€%5!%2#!"&55463•ึ*ึซชชีชช@Uภซ%!2#!"&5463•ึ*ึ€+๋๋7!##5#"&55#53535#532•VV*ึVV*ึซซ•*VVึ*Vีซ*ซ@kภ•%5!2#!"&55463•ึ*ึ•ึึึึ@•ภk%5!%2#!"&55463•ึ*ึภ€€ซ€€@@ภภ%!2#!"&5463•ึ*ึk*ึUึ*@@ภภ#2#5#553##5'3#"&=4633##•+UU+UีUUUU+ภUU+ซUU+UU+Uี+U@kภ•%5!2#!"&55463•ึ*ึ•ึึึึ@@ภภ%#7!2#!"&5463*K๊:*ฆึ*ึ๚eL3C*ึUึ*k@•ภ%#2##"&5463kึึึk*ึUึ*UUซซ%!2#!"&5463€€++‹ี‹ !!!!!!+ชVชVชV‹+@+@*@Uภซ7%!ˆxxศ€ภ€ีี+ช@@ภภ '7627#บ'P'2€์P์Pj'P'2เ์P์++ีี"%3572#!"&5463#53533##ซช€ภช๋+++**+UVช@**@ชVภ++++*U€•• %#55733#•*@dภซซ€ใ$$€*+€ภ• 3#%23#5767654'&#"#476+ชช4V=ธY  ..*ชK B$ a  1$U€ซ•%#55733##5#535ซ+@dึVV*VV€ใ$$*V*VV*V+€ี• 03##5#5353#5767654'&#"#4767632ซUU+UUึธY   .  kV*VV*Vว$ a   $# จ€Y•)4'&'&"327655432#"'&'&5+      „XA((((98(9t>,;?๋๋"%!2#!"&54635#53%!!"&5ภี+ี–+U๋Uซ•+ีVี+ีซ*ีีซ+U๋๋(1%#546335#532##!2#!"&5463!!"&5k€+UU+ซี+ีUUซ๋+U+*+*V+ีVี+Vซ+U๋๋!%5%##535#535#532#2%!!"&5!2#!"&5463kUU++UU ีUซซี+ี๋+*++*  Šซ+U+ีVี+๋๋!%!2#!"&5463!!"&5#7ภี+ีUUซ?L๋;*•+ีVี+Vซ+UqdK2๋๋&%!2#!"&54635#53353%!!"&5ภี+ีซU*++ีUซ•+ีVี+ีU€UUีีซ+U๋๋.%##535#53#32%!!"&5!2#!"&5463kUUU€V+ีUซซี+ี๋+*€*+€ซ+U+ีVี+๋๋+435"&554633#32#!2#!"&5463!!"&5++VV+€ี+ีUUซ**U€*+*++ีVี+Vซ+U๋๋ #%#7#53!2#!"&5463!!"&5*UU€Uี+ีUUซภซ**ึ+ีVี+Vซ+U๋๋%)9B35'35"&55463"&5546332#2#!2#!"&5463!!"&5++++ + €ี+ีUUซ**V++ซ    ++ีVี+Vซ+U๋๋+45#72##535#"&55463!2#!"&5463!!"&5@++UU+ซี+ีUUซ@++U€+*++ีVี+Vซ+U๋๋#7@5!!5##5#535372#!"&546335###535#"&5546332'!!"&5ภี++*++*+ีV@@@๋Uซ@€ี€**+++ซี+ซU+@ซ+U@@ภภ%#72#!"&54633•••ึ•••k*€ซึ*ซช@@ภภ"+2"&453##52#5#5#546333#"&55ๆ4&&4&ี+UU+Uี+UUUU@&4&&4ฏUU+UUU++UU+ีU+UUซ*%264&##54&#"#4&"3%#!"&54676632•&& D1:$)5+2F22#):?,๋5KB0L-:X€&4& 1D/ E,#33F2ช=*,?K51I'0I++ี !!#'#2#!"&546337€+`JKaVชVUUUี++KKีVี+UU€๋€!77'+ภ*€`"<€ซ€Q๋๋%!2#!"&5463!!"&5ภี+ีUUซ•+ีVี+Vซ+U ,,ิิ %+1777&7677673&"&462&'7#67&'7'&'5'67y%/?i/$3?r+)e&4&&4ร)M+)N)M%/?i/%3?[+%)k&->3ก4&&4&U/$3?~%/?3r/%3?+%)1ฯ๏@6264&"467'&&56&5467676'&'&&547F22F2z-%"@@ ## @@""@@ ## @ซ2F22FT&E ;!%%#;;#%%!;;!%%#;;#% ๋๋ #'753'777&2"&43#7'7'#5'7#5๋*..ฆ.„4&&4&ซ€€..U*'.€€€....ศ&4&&4*..c€€‘.s**@ฺี 3'7#'##7!335#g2E)D)EีีUU•@]N*ภ++ภภภ++•ี'53''5#5'k!ตึVฯOXM@j+9ต.ช•ฐYƒภOk•+kี335#•ึVV–@ีชภ @ภ๋ '+/%53'53'535332#!4633#3#"&5%5353•++€+*+ี*€+ซUUUUU+€+@+ซ**ช++++€ึ*ซ+ึ+ี++++ @@ภภ+/3?CG5!3335335355##5##5#2#!"&54633#73#'33#5##5335!#3•ึ*++*++++*++ึ*++ซ++U*++*++€***€€*++++*•++++++@ึ*€+++++*****+++UUซซ'/7?2"&42"&42"&42"&46"&4622"&462"&42"&4ฤ""o""o"";""‘""ฤ""""o""ซ""<""<"";""ฤ""ฤ""ฤ""<"" ๅๅ  $=%3'5'#'5'#5#33'!"&5'35!#'!2'5#'35#'5#'35#'U* J* JVVaูส+ถ+UV+J*+JV*+JV*UJ V€J V€VVa6+J+VV*ถ+*VJ+*VJ+ ++ีี #35#5#5#5#5#5#5#5#5#2#!"&5463ซVVVVV*VVVVV*VVVVVVชUVV€VV€VVVV€VV€VVVV€VV€VV€ชVอห!0'#5'#5##53353'#'32'735#'532#'#5b6ขH + +u๗ (  `++K  หcอขI i55€++u•  ) `i -+@ภภ@ +%5#72##553#5##535#3#'##532**  J@ + @+K  K เ@@` @ €++€55€+  -++€ €๋€6264&"62"&4$2"&4Z""F22F3!jKKjKี""f2F22F]KjKKj€๋€$264&"62"&4&2"&4HF22F3!jKKjKฃF22F3ซ2F22FฃKjKKj 2F22F**ีี 'L%7'6"264264&"&264&"'7'"264#"''"''&477'&47762762cNNM  I    $MMNป  eU\ TU]UU]UT\NNMNz  I    NMN#  4U]UU]UT\UU\@@ภภ7!'#!"&5463!2ตJ*`Jีึ*เ`€`5*+Uีซ#%!2#!"&5463#5##5#57#5ซชVชซ+*+ี***€+€++++V**V++@@ภภ"%#53733535#5#72#!"&5463kkk*ึ + ++ ๕ึ• J*ึ๕ ++ ++`ึ*€๋€!77'+ภ*€`"<€ซ€Q@@ภภ $)%463"3463#463"#52653#5265##5+W>,?+&๋ŠaPp+W>,?€ŠaPp€&@>W*?,&@aŠ+pP€>W*?,aŠ+pP&@@@ภภ -363"'63"'657'#47'#47'#527'#5277'65H9?/*B" ‰"ภe=*++85BVE5)2"X " "+B" *?9*/›="2)5DVB58++*X" ++ีี2"&4จฐ}}ฐ}ี}ฐ}}ฐ@@ภภ(54&##3#3#326554ੰ#!"&5463@VV++VVVี ++*++ ฎึ*•๋€ 2#4&"#462#4&"#4ŸยŠ+p p+ฎzX*?X?*€ŠaOqqOa5X>,??,>@@ภภ %5##5#32#!"&5463@+*+U€ึ•ึVV€V+ึ*@@ภภ!5#3#326554&##572#!"&5463@€UUU*ชึ@+€++++€ึ*@@ภภ#'5#"3326554&##572#!"&546353@U**ชึ€*@+€++€ึ*++@@ภภ%5#32#!"&5463+V+•ึ•ึ+ซ+ึ*@@ภภ$54&##3#"35#532672#!"&5463@UU*€U*Uึ+++V++ฝึ*++ีี6264&"2##"&473##5#535บŒeeŒeSฐ}ซX}๊VV*VVUeŒeeŒ}Xซ}ฐV*VV*V+@ีภ/%#2#5"&463264"32#!"&5463373ซซ->>-((((->>-ซชE&€&k=Z>&(:'‰(:'&=Z>*+++Uีซ3#!"&546333'33'33€Uช*@**+@+++@ซีVVVVV€@€ภ 3#"&4632€U3F22#ภUึ#22F3 k+™า%3!535&&5462€ึ€6GX|WLจS**T S7>XX>9T++ูา%"&462%3!5#546332#35&&5462nYL8@ซ @ ซ6GX|W9TS*jV V@T S7>XXซ€I€'7Ibb€€bbb€€ท€U€'7'ี€€bb€€€bb@@ภภ4264&"&264&"264&"264&"72##"#"&46h-W-•Oq?,%PppChฎdG,>  p pU๋ซ7!'#!"&5463!2ตJ*`J€€๕`€`5++ีี6264&"2"&4บŒeeŒeSฐ}}ฐ}UeŒeeŒ}ฐ}}ฐ+Uีซ!2#"'&"#"5432276#"'632ษ `ฦ` `ฦ`N]XSRYXSซฦ##:##7่U+ซี 73&47##!"54764'&543!2Œ่่ฦ##:#USฐSRYXk `ฦ` `ฦ+Uีซ2"/&4?"2764'ณš]]š]๐ŒVVŒVซB†BB†B9r99r9@@ภภ7!'#!"&5463!2ตJ*`Jีึ*เ`€`5*U+ซี 7!'''7572#!"&5463€R@.@56•kmR7๎ซ ซ*ชV+Uีี#6264&"332#!"&54633462"ิX??X?+€'DชD#(8((8•?X??X*ว8((8(++ีี 3!!"&57!'#!"&5463!2+*+ีภ@V?ฟ€ี*ซUjO++ีี $0@53'!!"&55375#3535#554&##326'54&##3532672#!"&5463+๋+ี•เ@ 555k 5  ถ @@uี*+K €* @@ €7 €*ญ@@ภภ#%!2#!"&5463#5462&"&462•ึ*ึ๕ภB,++,>ZZQ้NNNŠŠ‹r8Ÿ88 ,+|,,EZ[EพNNN<ŠŠŠWWซ๋ 5664&''77&'&'7#67?WW?.==.aa~09A+ฉ`‚`+G^GS_aŽ+Ž%._-&UWฉ๋  %673677#&'7'5&&4675h+r$0”+Wa.==.?WW?ถ.%พ%G_SG^G+`‚`B@@ภภ%!2#!"&5463•ึ*ึjkkk*ึUึ*kUU€๋€#%5##5##5##5##5#%2#!"&55463ภ+*++*++*+€€ซชUUUUUUUUชีชช0ล &75#"&6264&"#"'&5477632&&77}J"  C  jž  ส4[‡ฒ๛  ฆ!A  Aฒ }ภ+Uีี %7'#55372#!"&5463373@KK€KK€kชD'€'ตKK66KK6ภ**+kี• %7'#55377'#!"&5463!2KK€JJ€kUU ี + ตKK66KK6JV๊VK  ++ีี %$"&'3&"&462"&462264&"2"&4%J; ฺ žƒฑŒeeŒeSฐ}}ฐ}‹)!!aำeŒeeŒ}ฐ}}ฐ@@ภภ 777##73546335%&'ฦ๚ฝฝ*Uซ*Uฃ=๚`ก@๚=ฝ+U+U*๚=ปข++ีี6264&"2"&4$"'75บŒeeŒeSฐ}}ฐ} LJj&ZUeŒeeŒ}ฐ}}ฐ(LhL&Z€~‚U\%54'&#"2766'432#"5%"3#"'&533254.'&'&547632#4'&%73#5  ,{S)RS , 9)&!# * !** Ie+@ๆ6$45   Gm9)nn5  %'    &:%อY~ย‚8^"23#"'&533254&&'&'&'&547632#4'&"&53324##5327654#"#47632v , 8(!* !** ก*J/+ *. & *0&*!  %'   &*%&" R! & -$H@+ภ๋6264&"%"&4632753#5ย|WW|W+*p ppPA7ษ*+€UX|WW|ต5BOqqžq+ ฌ€€++%ภ๋/%27''#"&547'537#5'654&#"'632'$ฬW‚{628Pp ;ฦ*+€ึ* W>(" 0:C5Uฬ")>XV…6 qO:0;YI฿++L5B:0")>W *++ีี !)%67#67#67#53&''3&''3&2"&4ฅ”p  >” p>J?WWฐ}}ฐ}ี@>ภ@>ฒR`‚`v}ฐ}}ฐ+ี๋532#5#3'35#"&55#535#7#!ี€+€U+@@*ชUU+@@**U+€€ี+@@+ช++@@@@ภภ #533##53%3#5#53#533#3#3#@+UUUีีี++UU€**ซซีีี€€@€+*+U*+€+*ี€+***ึ* @kี• #'+/3#53#53#53#53753'3##5353#53#53'53€UUUภVภUภU๋UภVVjUVภUภUUU•UีUUUUUUUUjVVภUUUkVVVVVVkUU@kี• !!5!!53@•k•k€•€ช••••Uซ!73'#373%3#'#'#"&4632373%7(D+D)D',% &R1GddGR3 " ึซภภ*–ภ‚‚ +4eŒe@‡‡‡NNUซ#!"&54676632):?,๋5KB0L-:X*=*,?K51I'0I!๋เ!%773#'"&546753#553'7p&@@k#KjK#€๋@ึ*ษ&&}&ส+„:#5KK5#:gภ++??S'' !๋๔#'77532"&43#7'7'#5#57'7L&&*JjKKjK+@@;&&&*–@{&t''5??jKjKKj +x&K&&5??ิ++y&++ีี5#772#!"&5463!!"&5ซk56V+ีซซ ตUี*+@๋ภ7!''%2#!"&5463k*`J6 €•€`@หึ*@๋ภ  $)-16:>73'''!!"&53#73#'#463#3#3#%2#3#'3##553#3#@ีD5'`+V**U++€+;****ช++ซ+++ซ++ึ++++k[E.fีU+++++ซ+€+*+€*+€+ึ+ช*++@๋ภ  ',049=A#53#5#57#46#5#5#5#5"&5532##5'#5#57#5#5•*€+€+++e***++ี++ึี+++€+ึ++++ภ++++ซ**ซ+ซ++U++ซ++++ีU€€*++U++ซ+ช**U++@๋ภ6264&"%2#!"&5463นŽddŽdk€€KjKKj๕ึ*L!ด๔ 7'77'7#53''3#5!j&D&B&y**&…**€*V&$&๙'z?_&[?๊€€%17!##5#"&55#53535#5322#&&''267"#"&'3ซ++ช+++ช€€d“ D5Q_Qd“ Dซ+++ช++ึ€+€+ˆc<`Q6Qˆc<`++ีี$,4234&#264&"73#!"&54633732'52#4&462"U*X??X?ึjชD'€5K;ย(8((8€*๋>X??X—๋+@gK5);:((:'+๋๋ $064632"264&"'535332#!"&=33##5#5ั(((:X??X>+@•'Dซ+@@+@ธ:(((&>X??X‚@@+ึ€@@+@@++Uีซ(7''7''3#!"&546333'33'33i,,,,e;;;;ซUช*@**+@+++@,,,;;;+ี@@@@@@@ภภ"''77''773#!"&54633#!::::k,,,,*ีภภ+:::*,,,,ภ*+ึk๋•73''72#!"&5463#3#3#๋ีD6&ต j**V++•ZD.ผ  ึ*ึ@ภ๋77''%2'&5463ีภขL*ญญซภขLึ์tt++ี %7'#"3537"''&47762+JJk *VคภภภหJK5 U@ภภภ+เ%-5=$264&"62"&4'#5'&54776323"''264&"62"&4$"&462v>,,>+Z>>Z=E/*E <) ,?-ฮ>++>,Z==Z>\""K+>,,>Š>Z==Z^1„j< < ) +-๘+>,,>Š>Z==Zษ""U@ซี 15!264&"264&"'5462##"&55###"&55&€าฎ+XฆX  ช  kk€ี3""3ี&  &@@ภ• -!'#264&"264&"%##"&55!##"&5576332k* ๊ื),   ,๊`สย€ซ  ซ€%๋"575'&77546335332#"'"'#3#"''##532727€€€ิ(@€@(1%%`%%1V**-)UU)-**.('\'(€U**U๋Ž c@@c Ž****++,,+U@ซี )5#264&"'5#264&"2#!57"&554€k=Uk-ฆX, ,kk€mkk€-"3ห+ +ห3U@ซ๋ 5!264&"'5462#!57"&€o""€XฆX, ,+jj–""เ3##3เ+ +U@ซี )5#264&"'5#264&"2#!57"&554€k=Uk-ฆX, ,kk€mkk€-"3ห+ +ห3€•เ!3735'735"''&#"#3576"&462ั<-',+- .G> o+&`""Bำซ+€ +@5*4"/dHI""++ภี''575575462ู็ซ+KJ*ชชซ@•5u  u5*ku  ukU๋•2#5!#335"&462•#3+€++ซ<4&&4&k3#ภ@@@ภ–€&4&&4@?ภี %&&'77'7 ‘&ภภ& #ภภ#ซq••p?{••+?ี๋''77'&&'7''7'7FPjภ#K- ‘&EZ•;จ>ง๋pQR•{;"q6Z.จ0๋++ภี%''575575462ภซ+KJ*ชชซซ5u  u5*ku  uk+Uีซ3%!2#!"&54635#535#"&554633533#32##ซชVช–+U@ *+U@ €+๊* @ * @ +Uีซ &%'7''763#!"&552654ᕗ!2"LFZ!![GLซชVšW:TT:W1F"UUUU@@ภภ37!!3!535'5!Ÿย&๒Gซkkซ€k*ภj++jภ++@ีภ75!5#72####"&55+€+++2#€#3@++@@k@@#33#ี@ภ๏ -8CN7!'#264&"264&"%##"&55!##"&5576332&"&54677"&54677"&54677k* ๊ื),   ,๊๋ Y X ๋`หร€ช  ช€*  %   %   % +Uีซ !5##5#3'5#3#35#573#5##35!U+U@**@+ี@ชVช@*k++@++@@@๊VV@@+ภี7!2654&''!##"&'‡ เ m4& @€€+ึUVV๊&:H+D{@+ภ๋17="26447&546325462632#"'"&55#"&"&52463,,พ ,   ,  ‰PpPppPp‹ ,,F!!   !!  ไqOqOOqOqU@ตภ 5264&"5#%"&55##46332322655#"&547'7w  k€&, ึ€  "-+   jj;หk U•` š$-+ภี#+$2"&43!2##33!"&5477'#2"&4Z""ีF< L Ÿ๗M+o""€""o* Š#+ 5ขี""@@ภภ %5#5##33572#!"&5463€UVUUVjึีVUUVUU๋ึ*U๋•2#5!#335"&462•#3+€++ซ<4&&4&k3#ภ@@@ภ–€&4&&4U+ซี'-6264&""2646"26472#!"&54637"หjKKjK  3  •Dx2FUKjKKj    7ชVฤyG2@ภี"&46263"44&&4&@PpoQQopU&4&&4qK๊LL๊@+ภ๋ '6265#"&5#6"342#!"&54633462ิX?+&4&+…4&€Uึ*?X?๋>,&&,—&,??,U@ซภ +5#5#5#'5#5#5#3#5##5##33533€+++++ช++++++++ช++++ช+@++U**V++ซ++U**V+++€++++€+++++ีี264&""''&5546332ht – $ ภ – ku $ – ภ – €@•ภ264&##72###E@5KK5@U"VซKjK€€@@ภ๋ %5#5##3357!57'5!7U@*@@*ซ++€++2ี+@@+@@ภ*€€++€€*VC@@ภภ7632#"&5463320]/ $( –ี K  ]0/  K ี– ($ @+ภี6264&"&264&"&2๏""@"""ๆMภภภ""ข""@UซU+Uีซ &%'7''763#!"&552654ᕗ!2"LFZ!![GLซชVšW:TT:W1F"UUUU+Uีซ5'72#!"&5463ซซซซซชU+kk+jภ+@ีภ !5264&"5#72#!5#5463€   +ช๊&UU&ภUUภ  กjj๊&€UU€&::ึม''7&6766'&47=“““ะ L. @จZ– “““ะ@ .MZF•+Uีี#6264&"332#!"&54633462"ิX??X?+€'DชD#(8((8•?X??X*ว8((8(U๋ซ )$264&"7#3264&"%#"&5#"&5#5463!r@5_จK@+&4&€&4&++uญ5‹อUk&&&&๋V@@ภภ 1!'#264&"264&"%##"&55!##"&5576335332k* ๊ื),   ,5€5`สย€ซ  ซ€++@ภี"%54&"6"26472##'#"&5463€XPX˜0""0"[U@@Uซ%%ไ"0!!0hี@@+@@ภภ%5'2'"54777@€๕ x€r x€rk-( พ)-, B)-,๋๋'6264&"%3##5&&'#53667532"&4ย|WW|WT,,`C*C`,,`C*C`F22F2kW|WW|S*C`,,`C*C`,,`2F22F`@ ี'' ‘‘ีz@@k+•ี 7!!3264&".5462k*ึj"‘@ +GKjKU*;""++u%%4|+5KKk+•ี264&"&2.54๊,, |W,+4'  ,,ซW>PF=EAR>++ีี %5##376''&%2#!463€u+`5“&“+ีUี++“&“หU€::ึม''7&6766'&47=“““ะ L. @จZ– “““ะ@ .MZF•@@ภภ !7!'''265##526572#!"&5463k*`J6J>W+>,&๊ึ€€`@ X>,?kA'*ึ*@Uภซ%5#%##5##5#57!'!5€@+UึVช€UUU€€€€+kkซ++€๋€!77'+ภ*€`"<€ซ€QU@ซภ I2654&"264&#"264&#"73##"&55&&535&&535&&535463323๎$$$$ึ$@$ ช $@$@$@ ช @$@…""„""ง-, ,-, ,>•เ"7'77#5726323"'#5'6"&462ำ•i"'*o ?G. -+-I""c+ญId/"3+5@* €*ห""k+•ี 5#5##335&2.54U@*@@*S|W,+4' +*@@*@@ชW>PF=EAR>k+•ี 6''&7'62.54>PGG|W,+4' _PGGีW>PF=EAR>@@ภภ#''5ภก8’ภ€’8k+•ี"6274&"6"2654&2.54ษn;4;f""i|W,+4' ี.จDW>PF=EAR>@@ภภ %#7'7#57'53'73''7ภ€1>=ฯ€1=>O€1>=ฯ€1=>ภ€1=>O€1>=ฯ€1=>O€1>=@+ภี463#5#'53#5&&553353UA*56j*."5".+*+€7Vชk••"1ภภ1"••••U@ตภ 777#536264&"7"&55##46332322655#"&547'7ซU+U+ฬ  ;, ึ€  "-€•k K  /หk U•` š$-@@๋๋ &54633462"632##54๕)ั š ?X??XH4@!•€9)ั *NX??X?G' u++ีี"&.6%54&#"337335'26!467623#462"6462"€B>;E!$ < !+.V.++ขปีี  ‰  ญ“&&“! !.D-่-D•k3    U@ซี 0$264&"'35#5#264&"2##'##57"&554>R+kk*k€SX, +*Q*0 ,55•ƒUUUU–-"3ห+ ++ +ห$k+•ี *%5#264&"7#3#'##57&&554677#53#kึ]ต +*Q*0" D;fึF?Aีkk`& ** "$ด) *+ีเ!(/3735'735"&''&#"3576"&46255#573#'7{;-%.+, -G1  p*&a""ซ55u5uu55Bำซ+€ก,@7*".dGI""ท%56& Z %56 UUซซ #%53'53'5373#53#535335353UVVVึV*VV€VึVVV*VึVUVV€VV€VVVV€VVVV€VVVVVVUUซซ!'7ซwซซw*xซซx•ภk+3•ึk+k++ีี %7#&2"&4Uชฐ}}ฐ}ีVช}ฐ}}ฐ•ีk@77•kkีkkUUซซ'7!5!'ซซwwซซซx*x++ีี %'7''72"&4kMMMMMMMMฅฐ}}ฐ}ณMMMMMMMM@}ฐ}}ฐIkภ‰77'7ภโwงโwซ€I€'7Ibb€€bbb€€ท€U€'7'ี€€bb€€€bbkk•• ''7'77•wwwwwwwwwwwwwwwww€ท€U''€bbU€bb€ซ€I'7b€€bI€€bkk•• 3#5#53#5'53#3#5+j*@@*jภj@@j•j@ึ@j*–j*@V@*jkk•• 3#5353#'53#553#5U@j**j@ช*jj*U*jึj*@๊@j*ช*j@@€ภ€ !!5!5!@€€€€€€+j**k++Uีซ+2"&462"&4&2"&4๏""š""ๆ""+""""""ีU+ซ62"&462"&46"&462๏""""<""ซ""š""D""VUซซ7#7&#"32673#"&4632y2–E&45KK5*B ,\;FddFGy2–E&KjK/&8HdŽdžUbซ '777'bbbDDฆbbDDbbDDศbbDDž@bภ %7'77'7DbbDDbb|DbbฤDbbUUซซ7'#Uซซw*xซซwwk@ซซ 353#'๋Mฤ+๏M€@MึM€U@•ซ %'7#33'7•€M๏+ฤMภ€MึMUUซซ'737ซซซw*xซซww€€‰€ 3#%'7€++ €€b€€€bw€€€ 3#'7'7U++€€b€โ€€bk•แ'264&"264&"7!547'76275!"&7  t  ญ=ึ=-1 D 1ๆ*W|W@    W-KK--11UU>XX@+ภี!%'73#5'7'753'64'7((<\\zbwwbฬ!V1 ค)QQ)\\yขbwwbขd2z4*X*V1+Uีซ6264&"72#"&463#53ฤ""*GddGFdd***ี""ผeŒedŽd@kU**++ีี%2654''7&#"62"&4Fe%๏0r%๏09FeSฐ}}ฐ}UeF90๏%ซ90๏%e}ฐ}}ฐ++ีี%654&#"27'2"&4‡$eF<-i<-๐$eฐ}}ฐ}—-Pp –ี K  //^/ $&+?,>W+pPK K ี– ($ /\1/  @@ภ๋:54&"32##"&5546354622#"&54633276šZ k , –ี K  //^/ $ซ  U U   K ี– ($ /\1/  Cภ(%"'&'&55&""''&476 %#53#7๚5 .h. 5i"๚ €K`€•œ5BB5d‹K€ `€–@@ภภ $3#2#"&54633276#5•++ –ี K  //^/ $+ภ•v K ี– ($ /\1/   ••U+ซี 5##5##5#72#!"&57€+++ซUVVVVVV€ช€U+ซี%5#5#2#!"&57***•๋jjV++@ช€++ีี 5##5##5#%2#!463k++*++ีU++++++ภU€++ีี5#5#2#!463***ภีU+UUV++U€Uซ๋%55"&5472'654&#'7UUFeK5FeK5UU€@UV@eF2)!5K+eF2)!5K@UV==ซซ #)'654'57'567'7#7&5477'67ซ33 &/๎O2 ฌ&/€33f ซ33E0*4&/€ฐ2,ฌ4&/€33E0* @Uภซ%753756654'553'4677#7&๋*ซ22H8&/%0U*ีH8&/%0€22๋€€ภ32F;\, B*5%/€๊++k;\, B*5%/€32k•๋ %'353#2##"&5463UUU@*Vึึึ๋VVjj€*ึ€€+•๋!)2##&'3#&'54632#4&#2#52#4&#k-Aึja‰*qO&@>W+>,๊•+*€ ๋‰bOq€&–X>,?@Uภซ -!'#264&"264&"%##"&55!##"&5576332k* ๊ื),   ,๊+`หร€ช  ช€@ภ#%#2##"&54635373#53'53Uชตภ๕+++k+k+k*ึUภ@ีึึซ€+ึึ+€€++ีี%55#3572#!463€Uซซ€ีUีซDDซEปU€๋๋ /7M75"&55'73#"&54632####3232654754&"32##"&554635462ีgV+}XY}}Y!+ +€  ,/Y j ,A*fAaทY}}YX} 6* * @/Dฒ  U U  *€ีk&&66!##5#%!532ซ&4&&4&ช€ช€ช๋ภ#2&&4&&p*++j*€3 k็ฅ&&667'#5%%7ด0303ฅ•aช0๘-ถ"1030g(’(""`(_yB @•๋k 2!5335"&462•#3*+ซ<4&&4&k3#€ึ––€&4&&4+@๎ภ%'#"&5533276%33#"&553็ OI•&€K H t&€€,>* $•&ซ€• e&+?,ภ@@ีภ%2##5#"&55332%33#"&553ต  `•&€kึ&€€,?+€•&ซ€•€&+?,ภ@@ญภ !33#"&553##57#"&5533232k&UU,?+?`€&€k* &+?,ภฆ@U&ซ€•+@ีุ!*%'#"&''&67367'#"&''33&&66Z{ Q’##040>c–(<***$?#ภ` @~  % ."WU+3'ัส# #U5ซึ"*%'#"&55463323"'333#"&5536&462ซKl&9>8Jฟ&€€,?+/"TK&{!//O&+?,ภ""+Uีซ (5#5#5#7"3#!"&552654ᕗ!2*****๊ชVK**`**`** "UUU+๋ี!2#!"&54633'77ภ••€€€ขFUUF+VU++FUUF@๋ภ5!2###5#"&5463U•€€kชkUซึ+++@๋ภ%!2###5#"&5463ภ€€kชk•+++€@€ภ2#5'5463353353VKjK*V*kuK@@KuUUUU+ภี)"&462"&462#5#76332#5#546332#r$$ู$$ต@@6  6@๕ @ €$$$$’€ขข€ uu U๋ก 762&"62'6 &"k>ฏ=*,~,+L@๋ba+PเP๋==+,,+@๋aa+OOU+ซ๋ +%5#5##335'354&"2#!"&5546335462U@*@@*W„'6'ย?X?ซ*@@*@@ี++''Fึึ+,??,+U๋ช (763&'77&'7!66&%2#"&54776k@\1%ึ  +'1 E3*;›O=w- 4o๋@=% >*'<3;/9)-€ ํ ๘@ภ๋#'663232'354&"'#!"&5547'7พ';),?t'6',€'(6?,+ฒ+''”ึ +U+ีี +5#535#264&"73#"&5#"&5535#'732k@@ซซUI  ึ*ช&4&+–V@@๋ี@@++ึ@@€  3*&&@@*@@+ีี73'#''#"&547'#'632'3•J+e ;6CX}&<VJš6CX}&c๋*ป`<&}XC6;Ÿš&}XC6d++ีี %5#62"&4kึฐ}}ฐ}๋**๊}ฐ}}ฐี@+ภ 3#462"ีVV$$ภg$$++ีี %3#'&&46ฟlLLlฟ+Qoo๊LlชlLฟVzฆz++ีี %667#&73.2"&4:S”ภV@?i”Sงฐ}}ฐ}WS:V‚`R”:S3}ฐ}}ฐUUซซ462"462"&4632#";V<K$$@&! .. .. !&++• b b พ ??  +@ีภ#'+/39%#57#55#3#3#5#5#5#5#5#5#5#5#3!3€+++Vซ+++++*******++++++++ซีVีภ++U**ชี+*++***V++U++U****V++U++U**ี€k๋• $%2#54'6"2!5466"&462"&462UB8€*น8B7ี8x4&&4%…4&&4&๋$55,$55$:&4&&4&&4&&4€•#/62!54%#54&"&462"'64'632'##5#5353ํPX%8@‘4&&4&+  &&๏@+@@+๋#++++"H&4&&4&D&4&+@@+@@ @@ภี #,%5#5#5#5#5#5#'5#5#5#73!357•***V*******V*****ี€€€@@ภ++U****V++U++U**ช++U++U**ชี+*@@++ีี %$"&'3&"&462"&462264&"2"&4%J; ฺ žƒฑŒeeŒeSฐ}}ฐ}‹)!!aำeŒeeŒ}ฐ}}ฐ++ีี %62#66"&462"&462264&"2"&4J; ฺ "ƒฑŒeeŒeSฐ}}ฐ}ี)!!iำeŒeeŒ}ฐ}}ฐU+ซห%!5754675462"&53€+ช+3--3€Vซ++j2J    J2๊U+ซห!%54&"7!5754675462"&53U.N.ี+ช+3--3o"V•€)77)€++j2J    J2๊U+ภห'%'667372635462"&53'!57547'7€ฟ -3n$V„ห6++<วษ   J2๊:ะ9++k) ;++ีห%$"&537!5754675462&'7%#67$UV+ช+3--3*FSอG+S+าj++j2J    J'V3@g‰2Wg@U+ซห $5#335#!5754675462"&535j;;j;†+ช+3--3€V/&&I&&;++j2J    J2๊@@ภภ#2#75'3##5'7#"&=4633'#•kVVk€jV€€Vkภ€VkีV€k@Vk€ชkV+Uีี /%2654'###7"3&54633&72#!"&5463373,?-&U!4,?-&U!wชD'€'•?,&+ึ?,&+@**k๋• $%2#54'6"2!5466"&462"&462UB8€*น8B7ี8x4&&4%…4&&4&๋$55,$55$:&4&&4&&4&&4k๋•)1>"264"&462&"264"&46254&#"#54&"%2!546326q"" >,,>,๚"" >,,>,เG$& GHG++`*`+/11u""\,>++>I""\,>++>ก    `'::'UUซซ62!546"&462สluชฮF22F2ี/&++&Z2F33FU๋ซ$2!54'3##5#5353"&462 luช@@+@@+ใF22F2ี/&++&…+@@+@k2F33FUUซซ !62!54662"&4"!54&"264เ@L?ช?IF22F2XVo&&๋*@@*า3F22Fถ!  แ&&U€••7#53##5#5355`*6`VV*VV~๋โ U+UU+U@@ภภ %5##5##5#2#!"&5463k++*++ึ•VVึึ––+ึ*++ีี"*%654&'####32325"&55'2"&4~-;0* *€ ˆfWฐ}}ฐ}0C5V + + @T)fA`v}ฐ}}ฐ@๋ภ #5'7'๋+ภ๋V••••ภ€ซ”i€YRRVQQ@,ภี&%2"&547'#"&46327&5462#"'6€$%2%—&&–&4&&–˜ฉ%%% X&4&W &&4&X XU+ซ๒ 72654'"&54732654&'๚+; D<(C@KdŽdE,! )k;+,*) 5'‡4•TFddFlR!.-"4++ีี !)62#&"#6264&"2"&4462"6462"J; #b# ŒeeŒeSฐ}}ฐ}jƒี)!**!WeŒeeŒ}ฐ}}ฐ0++ีี#6264&"2"&4462"6462"3#บŒeeŒeSฐ}}ฐ}jƒ~€€UeŒeeŒ}ฐ}}ฐ0@ ++ีี !)6273"&'3264&"2"&4462"6462"ฯb# ;J; #ŒeeŒeSฐ}}ฐ}jƒซ*!))!€eŒeeŒ}ฐ}}ฐ0++ีี%-62#67'7'77'7''7'7264&"2"&4J; ฺ œ‰ŒeeŒeSฐ}}ฐ}ี)!!TC๏eŒeeŒ}ฐ}}ฐ++ีี !$"&'3''777'264&"2"&4%J; ฺ ฃ-.A.-rŒeeŒeSฐ}}ฐ}‹)!!x----ภeŒeeŒ}ฐ}}ฐ@@ภภ77''%2#!"&5463ีภขL*ึ•ภขLภึ*@@ภภ2#!"&5463!!•ึ*ึ*ภึ*+ึ++ีี6264&"2"&4บŒeeŒeSฐ}}ฐ}UeŒeeŒ}ฐ}}ฐ++ีี6264&"2"&462"&4บŒeeŒeSฐ}}ฐ}ฉX??X?UeŒeeŒ}ฐ}}ฐ?X??X+@ีี %7'77„#t™<<™t#P–e  e–+@ีี%'7'''7'77PG^$ีt#„„#t™<<ท0[>VCe–PP–e +@ีี %'7''%'7'77PG^$$^G%t#„„#t™<<ท0[>VV>[ดe–PP–e  DN2#&&''4##32765'2##5#"&53324##5324#"#476327"&'3d“ E5Qo/$ /5#11 #HQd“ D‡c;`Q๛9{"Y/ &ชS    62  ูQˆc<`@+ภี ##5###5!&2"&4ภ€+*+€€ั""@๋€€+j""++ภ๋ !53#5!3#'3#๕หk*@@ึ•๋@@€@@๋k++U–j@@–––@@ีภ %$264&"53#!"&5463!2#"3H5ีึ*ภเGชช+*ช@@ภภ7!54&"64&"2'463!2#!"&5€XPXภ&4&&4ฺ*ึ•%%t4&&4&•ึ++ีี627&&"6"264&2"&4ฏข/XNXš4&&4&˜ฐ}}ฐ}fE&%๊&4&&4f}ฐ}}ฐ+ร๋%-9733!"&5477'#53367##2"&4&2"&475#53533#™๗M+F$ –K%R Ÿภ""ผ""€@@*@@ล+ 5ข**+ Mˆ•#H""""ฺ@+@@+@++ีุ6264&"62"&47'5''7'7ย|WW|WE pp pหUeCbbHbUX|WW|qžqqžp2<€cR RR!S ++ีุ #3##5#535264&"62"&4%'7'7@@*@@)|WW|WE pp p•bหbb@@+@@+@๋X|WW|qžqqžL!S R R#+ีุ !1'7'32'#"&547''7''7"'632'654&ซาา"W>4๖L%/7GPp/ฒbs!**Pp! Wบ า*5>XzL่%//qOF7.!S X qO,( >W++ีุ 77'7264&"62"&47'7'7แi€D|WW|WE pp phbbHbสj€DขX|WW|qžqqž~R RR!S +ี&2L5##5#7!47'&6632762"&554$2"&5545!##"&55#"&55#"&@V‹54  AฉU * •=&AB%  ™ ••  •• ยีี KKKK ++ีี%5#75#72#!463***ภีUภ++U€€ภU€@๋ภ%!2#!"&5463#53#535ภ€€€U*jภj@j,ิVึ*€@k+@k+@@@ภภ %5##5##5#2#!"&5463k++*++ึ•VVึึ––+ึ*@@ภ๋ )5#5#5#"26472#!"&54633662kึึึ––t  €ึY * @++U**V+++   ึ*@@ภ๋-%54&""264&"26472#!"&54633662€XPXš4&&4&7  €ึY * k%%&4&&4{   ึ*@@ภ๋ %264&"5#5#2#!"&54633662๗  ****ชึY * •  อ€€U++@ึ*@@ภ๋$%5#55"26472#!"&54633662UUkk   €ึY * ภU@jk@   ึ*@@ภ๋$%7#5##6"26472#!"&54633662k@V@t  €ึY * €kUUี   ึ*@@ภ๋ #77''6"26472#!"&54633662ีซ7‰  €ึY * •ซŒ7ี   ึ*Uซ๋#'72654''"&54635eFUU5Kq5KeFUU[)2Fe@VU@K5DK5!)2Fe@VUUซ%3'337#!"&54676632+@kk@Vr):?,๋5KB0L-:X๋jjV•=*,?K51I'0IU+ซี7572#!"&5463€56•ซซ ซ*ชVk@•ภ 2'463k••ภซ@@Uk@•ภ%#72'463kึkk••€๋/ซ@@UU@ซภ75#5#7#3#3#"&'#53&55#53547#5367'7627+VVVึ-++-<;F;<-++-<#//#++U**ช*++##++*#..# ๋๎%''&'&&77'6ไ1ย$Q ^@\&X k1ย X&\@\ Q$U๋ซ3'34632&#"%##"'73265#€@UV@eF2)!5KV@eF2)!5K@UUFeK UFeK5+Uีซ!!ีVีˆซชฺ++ีี 77''62"&4ีภขL=ฐ}}ฐ}•ภขLี}ฐ}}ฐ@๋ซ%#2#!"&54633#53#3#ภภภ€ี––––––k๋@๋เ Š  U+ซี7572#!"&5463€56•ซซ ซ*ชV+€ี€ %7'7''77cc€€Œ€€cžbb€€€€b+Uีซ5!5!2#!"&5463ซชVชVชU++ี€€+@@ภภ 3#53!53'53ซซซ€ซซซภ€ีี€€ซีีk@•ภ!5373!##"&•ึJjหชซ++ภU+ซี 3'5#5##!"&5463vv@ชชช€€@uเ++U++U€V@@ภภ/264&"%2#!"&55463264&"%2#!"&55463„""@ ช /""@ ช @""f € € ซ""g € € Ikภ‰77'7ภโwฆใw k๙‰ 77'77'7 wZxY‡‡‡โwwYฤˆˆ@@ภ๋%5!332#!"&54633533#5•ึ๊+ึ+ชkk๊๊€+ึ*++ภkk@@ภภ 2#!"&553!!#54637#53'7•ึ+*ึ+l7ฮฮ7kkภึUU*UU๓8*8kk++ีี %72"&42"&4/QฏQ(ฐ}}ฐ}ห  ัฏQฏU}ฐ}}ฐA  ++๋๋/2####54&"#"&553264&##54633546232ต  Q"0"Q !! V,V,V !! Q"0"Q   V++ีี&62654'#"'2"&42"&4&2"&4บŒen@!OSฐ}}ฐ} pUeFZP# F}ฐ}}ฐS+9ีภ%'.5463263250;C2:&&:2C4=E90.D71D--D1'T>>+9ีภ-%>54&#"#&&#"2'.5463260.6+ +(+ +6.0`2C;05E=4C2:&&t+,<. ** .<,+ND17D.0>>T'1D--++ีี5#5#2#!463***ภีU+UUV++U€U+ซี!6462"'654&"327#!"&54633ภ&4&&4ลR?X??,^ ซ€ั4&&4&MR,>>X?_V€U6สซ!%'#"'5332673'"#66327#7&cgh(0>,,€6-'9+':+T8=,,€6ฝhg,,€60%$ค0%6J,,€6 @@ภภ"&+/48<%5353!!"&553'5353"&53#532##5#46#57#5@+++U+++++U*ช+€*+++++•++++*+ึ+ซ++U**V++ี+++ซ**V++ @@ภภ #(,075335375#2##"&554635353"&53'53'53•++*€ีีี€+ี++++++@++++€ีีีี€++++*++V**kU•ภ 7!!%'353k*ึ*••U€€+๋••€€+@ีี %7'77„#t™<<™t#P–e  e–++ีี$264&"&264&"264&"2"&4?, ,U,,U,, 3ฐ}}ฐ}‹,,ม,, ,,+}ฐ}}ฐ++ีี!654&"34623475#2"&4A2F2*" **Cฐ}}ฐ}#22#" ! !‘**j}ฐ}}ฐ++ีี6264&"2"&4''7'7บŒeeŒeSฐ}}ฐ} 7777777UeŒeeŒ}ฐ}}ฐ7777777@ีภ3'&2"'73264&"3''34 K[:žqqž8,=>XX|W@VS@UZ-7ีp p8,W|WW>VSP+Uีภ 7#5#7##5#ีj@ีี@jVUซภภซ€€+€ี 75#'3!!57'UชชUUชีUUUU UKKภUUK€€UU€€UU€+€ี !!57'€UUUUี€UU€€UUU+ซ๋'54&"264&"72#!"&5546335462B'6'1""ซ?X?U+''+ภ""ฆึึ+,??,+++ีี5#5#2"&4***Cฐ}}ฐ}@++ซ€€@}ฐ}}ฐ++ีี 53264&"2"&453๋*[ŒeeŒeSฐ}}ฐ}ภ*@++๋eŒeeŒ}ฐ}}ฐร€€@๋ภ75#53572#!"&553!!#5463๋ึึU€€+€€+ซ@*@UภีVV,VUU4ซะ%7"&477Z&Lญ2dŽd2y^5Z&54L๙2dd2y@kี•##"&5546332x]] ๊๊ƒƒƒึ@kี•%7'#%##"&5546332ULL๊ ]] ๊๊•kkึ๎ƒƒึ ++ีี #+05=%364'#67#'64'#67#'67&''3&47#73&&'&2"&4]HH)= ? d2 R 9 = = PHHซ R x = ญฐ}}ฐ}ี,‹8(R,,),,ึ($8ช8$(*,น),,,8$(€}ฐ}}ฐ@@ภภ3#5'7#53#!"&54633#+•+ััLj+ึ••ภ•Lััึ••*+ึ@•ภk !!5!%5!%5353'53•+ี+ี+€+++++k+ซ++V**U++ซ++V**U+ซ๋'54&"264&"72#!"&5546335462B'6'1""ซ?X?U+''+ภ""ฆึึ+,??,+U+ซ๋!)%5!2#!"&55463354&"#462"&462€ย'6')?X?Z""Uึึึึ+'',??,+ภ""U+ซ๋ #+%5!354&"2#!"&5546335462"&462€>„'6'ย?X?Z""Uึึ+++''Fึึ+,??,+ภ""++ีี*%64&#"'&#"264&""''&5546332p[ญt – $ ภ – บ,[ u $ – ภ – ++ี2#!"&5463353#35ซช+ซ€*€€Uซ€U+ซี3'5#5##335#!"&5463vv@@*@@*€@u๖*@@*@@*€V@Uภซ##5#72##535!3#"&5463U@*@๊UUึUU+V€€ึ+ีี+@@ภภ3#5'7#53#!"&54633#+•+ััLj+ึ••ภ•Lััึ••*+ึ๋๋ %3'35%5#535#'775#7#+@kk@k@@ภ@kkU@kk@ภ@kk@@k@V@@V@kk@@kk@+Uีซ %%7'654&"32772#!"&54632"&4f>8P88(ƒชŠ, ,|>(88P8๑k, ,+Uีซ5!5!2#!"&5463ซชVชVชU++ี€€++@ีภ /%54&"2672##5665#"&5##"&5463373+""€–->+2F2+>-–D'€'๋UUป-G/#33#/G-++@@ภ๋'%54&""26472#!"&5463353353€XPXš4&&4&Uึ+ช+€%%&4&&4fึ*++++CM$264&"7''##"5'&'''&774&465'&776677433276'"#&ˆo*  *OB^๗ซK%  %%  %ช^Bซ๗k•๋%#2##"&5463#57#5kึึึ€***k*ึ€€ึ€€V++UUซซ !62!54662"&4"!54&"264เ@L?ช?IF22F2XVo&&๋*@@*า3F22Fถ!  แ&&+ี7!''72#!"&54633!!"&5•+K5Kเซ€+€€ภ`@`kี*+ี*+@@ภภ!3#2#"&54633276ภ€@ซ –ี K  /0]/ $ภ•@6 K ี– ($ /]0/  +ภ 35#5#26๋****ˆxvU+ี€€[ฦ;Z@๋ภ%!2#!"&5463#5ภ€€€Uชj,ิVึ*U€€ U๕ซ #7'#'73•``U`8จU``U`8จซซซซc๒ซซซc@@ภภ"&5472654''#5|Dp pD7W|W7I*’:XPppPX:-G>WW>G,Mีี+@ีภ !5264&"5#72#!5#5463€   +ช๊&UU&ภUUภ  กjj๊&€UU€&++ีี '5264&"2"&4 `p1ŒeeŒeSฐ}}ฐ}kp9D€๊eŒeeŒ}ฐ}}ฐ++ีี ##463!22'#"&55!5k ีU  U U๋  V+ @ ภU +ภ@+ภี!%7777777''''''%5!5!5!@  +ช V ๕++U**V+++@ีี  A%5#&''7#5!"2646"2642#!"&554633&546327632ซm-#@@#-mVชt  t  Vช/&! !&ี€<W W<€j**@    ๊๊&&@๋ี %5#5#***ึ๋๋ีVVU++@•k@ีภ3'&2"'73264&"3''34 K[:žqqž8,=>XX|W@VS@UZ-7ีp p8,W|WW>VSPk+•ี264&"&2.54๊,, |W,+4'  ,,ซW>PF=EAR>++ีี '5264&"2"&4 `p1ŒeeŒeSฐ}}ฐ}kp9D€๊eŒeeŒ}ฐ}}ฐ@Kตภ6264&"'5'#"&462ฃP88P8เj j&4:QQtP!ี8P88P8j j!PtQQ:4&-+ำี?6264&"7''##"''&'''&77&47'&776677633276แ>,,>,๊-+ 5V5 +--+ 5V5 +-ต,>,,> # J8 8 J ## J8 8 J #@@ภภ7GO$4'76''&&''&##"'&773327767776''72#!"&54632"&4p % < %  % < % &ึ„""๖4 '(34 '(3ฯึ*•""@ภภ2#"'73264&"3'34"&462ฐ ppPB3(/>WW|W@VU@๋""ภp p(W|WW>UUP?""kz%'73#5'7'75353#53353=((=\\zbwwb@+ึ++*ฯ(PP([\zขbxwbข++++++€€%#2##"&546353#53#53Uชชช•+€*€+ซUซU++++++@๋ภ)264&5'75373#'!2#!"&5463&&o 5 5 5 เ€€€@€&4&•5 5 5 5 A,ิVึ*u๏‹ '7'537#553''7{tt!]]o*V+ซ+]]!tt‹‹‹pp…********…pp‹‹"๋๋ (2#4&"#4''75&5462&2#4&"#4ŸยŠ+p p+I@@I ,s|W*?X?*๋ŠaPppPa’FI@@IF $$นW>,??,>๋๋ "-9E%53#5&3#5354623#53546253#5&&753#5&&3#535462k€+*V+€+  ซ+€*  U€*+ซ€*U*€+  ซ** ZZWU€€U ^€€U ึ**-ZZ ** ZZ ?U€€U ๋๋ "-9E%53#5&3#5354623#53546253#5&&753#5&&3#535462k€+*V+€+  ซ+€*  U€*+ซ€*U*€+  ซ** ZZWU€€U ^€€U ึ**-ZZ ** ZZ ?U€€U k+•ี 353353353#5'53546332ซ***+@ช@ชซ@****@@€€@@€€@๋๋'3;$2"&462"&4264&"2"&42"&46##"&46332"&462>=ฅ pp p_ยŠŠยŠ“ซ@@ƒภ}ุp pp ;ŠยŠŠยกิ}@๋ภ%!2#!"&5463'''%'#ภ€€€๋++U5555€+Vj,ิVึ*๋66€V++++ถ6@@ภภ $(3#2#"&54633276'#5##5•++ –ี K  /0]/ $++*@+` K ี– ($ /]0/  ‹++++Uซี#!53"&5472654''#553#53@+ JdŽdJ=KjK>-**€+++ก4XFddFX4&H5KK5H%Sีี+++++Zฆ(2&"'662&"264&"72##"&5463cC8 8E ,|,Z;""k € E88E,,ก""f  k•๋#'#5&&5326553#536"&5546253•K5*5K$C\C1+€*4&&4&ซ++6RFFR6/>>/ี++++๋&€&&€๏+++@ีี77'7353#!"&53546332ภ  V*€ช€V€kUk+++๋++๋๋%%7'5353#!"&553546332!#!"&55uuU+kีjUภUีภU@k+++๊๊+€๋๋@๋ี "6264&"'3'32#!"''&54633762๏""€@of ( ๊ 6 f]•""‘^^ (‘ฦ Œ +ภี#+$2"&43!2##33!"&5477'#2"&4Z""ีF< L Ÿ๗M+o""€""o* Š#+ 5ขี""++ีี %5#5#5#'5#5#5#2#!463€ซซซkk*++++++ีUU++@++@++€++@++@++U€4 ๋ภ'7'3''##3อหlN—X,Ux-m(m หmOนvเ@@๋++ีี %'7''72"&4ZPi))iPZXฐ}}ฐ}€gE `aEg6}ฐ}}ฐ@Uภซ%5#%##5##5#57!'!5€@+UึVช€UUU€€€€+kkซ++Ukซ• !!5!5!5#5UVชVชึึ•*ซ++€++ซ**+kี• "72#54662#54&"&462"&462ภ3•7B—JP๊4&&4&v,,๋.05$ 00`&4&&4;, ,@kภ• 5#5353#'ภU––ึ––U@U@*@€@*@Uk@•ภ ##5#3'353ภU@*@๊@UU@*ภU––ึUU–++ีี %#5##'3353'&2"&4u5+5K 5+5K-ฐ}}ฐ}ภUUKหUUKJ}ฐ}}ฐ5๋ต2#!"&54633#!#5'3533ภ€€€€€@U@*@ตี+*ี+*๋Uภภ@๋ภ%5#5#2#!"&5463ภซี€€kีUึUึ*@๋ภ  ',049=A%53#5375353535353532#5"&53'535346353'53k*€+€+++š***++ี++ึี+++€+ึ++++@++++ซ**ซ+U++ซ++U+++++U€€*++U++U+ช**U++U@ซภ +5#5#5#'5#5#5#3#5##5##33533€+++++ช++++++++ช++++ช+@++U**V++ซ++U**V+++€++++€+++๋ภ3#2'&557#"&57#547763•VVU Œ ‡A ภี  b)–@๋๋3##"&55477323๋A ภ Œ ‡*V+)–ี  b/2'&5667#"&5547763'##"&5547732เ j o 1 P1  j o + ‹ i 4 qUq ‹ i 4 @•ภk %53'3#53!5!%5!%5!•+++++€+ี+ี+๋**€+ซ++++V**U++@@ภ๋3#5!2#!"&5463353353•kkึ*ึ+ช++kU๊๊Uึ*++++U๋ซ &&467264&"62"&4@/&8HH8&œjKKjK:ŒeeŒe*TB ,\v\, ์KjKKjเeŒeeŒ++ีี5"&4633"&54752654'7"&546752654'—>}ฐ}}X"$2F2&KjK=.?WeŒe2—>YX}}ฐ}ฐ  -,#22#!&45KK5/G+`AFeeFG2+๋ี #%3'7#'##''7&'367#53533#SE#`+e+`MBkm(+.๏–*–?5•]9@@l,Bjk,5(3?+**+P;+€ี€ %7''777U1hUž€U†1€1hUž€U†1€@ซีU5!5!5ีUภ@U@*@+€ี€ 3'''77U€1†U€žUh€€1†U€žUhk@•ภ 2'463k••ภซ@@Uk@•ภ%#72'463kึkk••€๋/ซ@@U@ภ๋77''&&55ีซ7€ภnRRn•ซŒ7V€Y““Y€+@ภภ2#!"&554632#!"&55463ซ • k • ภ € € ี € € U€ภ• 733#!3ซภ@@ี@€๋๋๋+kีซ 3#!533€UUซUึ€๋๋๋*@ภU€ภ• 3#!333Ukkkk•๋๋๋+@ภภ!!2#!"&554635!+•k€ • •ภ@+ € € ๋@@Ukซ• !!5!5!%5!UVชVชVชV•*V++ช**U++Ukภ• !!5!%5!%5353'53ภ•VVVVV•UีUUjVVkUUีUUjVVU€ภ• 3##5353#53#53'53Ukk€kk๋k๋kkk•€€€•€€€€€€•€€U€ภ• 3#53!3353ี๋๋€k•kk•€•€€๋€€U€ภ•!!5!Uk•k•€•€€+kภ•/2##"&546332##"&5463#2##"&5463 @ ึ @ ๋ @ •    `๋ 2"&4264&"62"&'6ๆ4&&4&X??X?ž€€ž€@&4&&4…?X??XฬXHHXXHH+๊ภ%532'327'#"&547'7&&'#"&'67&&"'632'654&&Ÿ ?,!&—z5+2O€7 +า.'.O1>?@&3,? !&—†4XH>, ,7.XH;*>,?+@ีี  A%5#&''7#5!"2646"2642#!"&554633&546327632ซm-#@@#-mVชt  t  Vช/&! !&ี€<W W<€j**@    ๊๊&&++ีี5!5!2##'5#"&55463ซชVชVVUUV+€€k++๋j**j๋+@ีี -%5##5##5#5!352#!"&554633546332ซ@+€+@Vชk€kช@€ี€****€j**@+++๊๊+++@ีี5#32#!"&554633546332+VึชVV€++๊๊+++Kีภ %'5'#"'73264&"3'34632kjk&4&! (88P8JXR5R9:Q"ีj k!8P88(UU9RQ:3'kk••!!!Žไ*ึ•ี+*+@ีภ#%''77264&"332#!"&54633::::GX??X?+€'DชD•;;;P?X>>X+++ีี#'2#4>54&"#4264&"2"&453F2@*"*ŒeeŒeSฐ}}ฐ}ภ*€2#9!#๙eŒeeŒ}ฐ}}ฐุ++@kภ• !!5!5!%5!@€€€€€€€•*V++ช**U++@Kตภ %##5#53533264&"'5'#"&462++++]P88P8เj j&4:QQtP!+++++k8P88P8j j!PtQQ:4&@Kตภ 3#264&"'5'#"&462•kkP88P8เj j&4:QQtP!@V8P88P8j j!PtQQ:4&ภ๋@+5#72###553##5#53##553#5##53ห++  + u` –` U +  5  +€ `` `` +€55€++@ีภ %#5463323#%3#5!#5#kึ€ภ@@j@@ภV@ึ๋ชj@@@ซ€€@@5@หี%&''55'!!+R|"j);q  ˜–jฬ n2ฐ ภ C+'@ฺด&'77'776!!ิ |Rq"1*jX)“r x–j?! T ™ ‰โ+€U€• 732653"&733'3€+2F2+KjKk*K``Kี#22#5KK๕w``kภ•@ ##3##53#2#353##"&5546;#•@++ `ี J* @ u   +€ @ + V €@@ภภ%5#%2#!"&5463kึึ๋**ีึ*++ีี 77''5#2"&4q)ึึฐ}}ฐ}ีq)œ++U}ฐ}}ฐZRฏง$64&""&4653#5!#35DDaDDnXX|VVlUUUUUˆDaDDaD X|VV|X@UซUUUU++ีี/6264&"2"&4"32653#"&5547632#4'&'&บŒeeŒeSฐ}}ฐ}า((&(*&!& UeŒeeŒ}ฐ}}ฐ:: 0*)  >+ฤี6Je|%#&'&54622654&#"'&476632"&54&"'3262#"'&5432"'&'&5462"54&""&76762'&'&&%"'&#"&47632>.!.&6'&[@.L W4Ig'6&&(* =7)  (3 9GdG:R: Š 02x21 -.l.-,;N,1 1,N>ย @๋ภ%!%#!"&5463!2#5ภ€ซ€€Vชj,ิ+€€+ี $(83'7'3772#5!!#3#535#"&54635#72##"&55463A5455Aภ*€++ซ+•ภkk k @&>''>&@Ukk++**+€––ภ ภ ภ eฅภ,%##"''76323546232'&546254&"’ ‘ j Iq+8P8* ,ญ q  jๅ€24(88(5Pk+•ี-5%3#"&5467326'4633"'32#5#"&54462",:',>0%&!6*$282@*k$$€%0>,':,!&ั '*)Iujู$$+kี• %'73#55#5@UU••€UU•๋UU@*V@UU@*@+ภี#'%5!2#!"&5463353353#5##5##5•ึ*ึ+ช+++*++U๋๋Vี+****–******++ีี%66737&&'5'&&4670I@hP€I0Ph๊2NN2QooQlH0Pi๊0HAhPVlVAzฆz++ีี %6737&'5'&&467 ˜iO( Oi๊QooQร Ml๊ ˜lM("("˜zฆz @Uภซ #'+!!53!5353#53#53#53#53753#53#53@€€ีซ€ซช+€+€*€+€+๊k๕j๕kซVU++++ซ++++++++++V******@Uภซ !!5!5!%5!@€€€€€€€ซVj@@–@++k•/$264&"'#53&&#"3267"&547##"&4633'#53{4&&4&ฎ<<!&&!)8>Z=;#9'->>-๗+L^•&4&&4+&4&’<+-==-;%0=Z>+*U2ซฮ7!4''7"&5477€&ZZ&๙2dŽd2yี7&^]&H2FGddGF2y+*ีภ+3;CK%&##"'&'&667667676363226462"&462"&462"462"r     'SS'  !   ",,,,Ÿ,,,,ร * ** (  (Q, ,u,, ,, 6, ,ซ+Uี %##5#5462&462"U@@*&4&*•$$๋Vjj–&&$$@๊ี'64''64'2!546462"ฌ>>#,,# $ธluชV2F22FีAซ>"3ƒ0%#X $-ช/&++&ŒF22F2 @@ภภ #'+/3#54&##5325335335353#535353'53533#ภ+&jj,?€+*++*€+€+++++++*++++Ujj&+?ฟ++++++U++++U++ซ++V**V++*+Uภ๋"'%'5'#56776332'532"&4#'ภ@@— 8L† Q""`6+K @@@ —.!{L1 †k""๚6J €๋€3#"'"&547'"'"&46327&5462627&5462๋L"7 a"a"6L"f"L7a"a6K@@ภภ'5#7&&27653"'&4627 J[ี‘;,{,+V|,++88 88pž8:UZ-7j-<,+,xX,+=O7887p8<++ีี %7'5#&2"&4Z` Cฐ}}ฐ}ฆ:o€๊}ฐ}}ฐ๋,##"''636325462354623546235462๋3#›$จ \‹ส#2ซ4  –หหถ  ถv @@ภภ'%27#"&'#53&47#536632&#"3#3#@3'&6J>cKAAKc>J6&'3'B{‰‰{Au"&1H8++8H1&")"++")++ีี!%AU%4&##3533#'326'6677#7&'4'#3#"&46327'&#"32672##'#"&54633ภ ผ)M ::+– –  U 8T/ !$33$%/พซ–€U V'$99;+ ฑ/ F ! * 3H30œ๊@@ๅๅ-62"&47'!23'#'#"&547'#"&5477''7„""แภ L วl+2L= Ÿ/^€""…ภ Š++#ฅ=  5c^U+ซี%%264&#"'3'632#"'##!"&5463,??,: U".,,'% 5L€€?X>1U"(+>, #U€Vี 2'35#'35#'3'5#'#'ซ•i”ฉซ~)++++eบzภU+ี•++)~ภ+k++FzUj+k@•ภ3!537377'7''!##"&KJึJj.-------Sชซ++ฝ.----...ถ++ีี+#'#'#5'75#'7#53'735'753737ีYEc+dE*Ed+cEYYEc+dE*Ed+cE*Ed+cEYYEc+dE*Ed+cEYYEc+dEk๋• /3'#264&"'5#264&"'35#%#"&5#"&5#5463@kV(@UN UU+€6&4&u&4&+VเwVVŠwV*€j&&&&ภs'2"''7264&""&462'&"27677]`CC`" <**<G5!D^D"*<*>5@@ภว&&6&&7&474367t2ƒ>0s1z11 2=??FbQ8™‰D2 11z1s0>ƒ2=?ณ?F8ธŠ+@ีภ+5#32##5##"&554633573353#!"&553+Vึ€V€U+U+€V•ึ•k**@++@*++*ภUU@@ภภ'7264&"264&"&264&"&264&"264&"2#!"&5463RNNึ@าNNา.ึ*@๋ภ%CKS73"&26723264&#"#&&""&#"3263%"&'&&'&4766767632$462"6462" ภ 4@4!fO  OfO  k _r^!!6I:]!ล[ี##M0% ++ภี(-$264&"264&"%#"&'##"&547'#53!'2#^ญ+*-),'-/J7ซGdซUE!+%%+,^++9๙dF++ีี%'''7''7'7'7777ท--LทL--LทLร--LทL--LทLU@ีภ7!!5#72####"&55UVชV+++2#€#3k+@@k@@#33#ีU+ภี"&5473462"k€. pˆQb‡BDf'<8R " %Z ` ` | RŠ `  *๖Copyright 2015 Google, Inc. All Rights Reserved.Material IconsRegularFontForge 2.0 : Material Icons : 8-2-2016Version 1.011MaterialIcons-Regularƒ2 '(ฬ 4latnsize  ,latnligah8Tโ์ จ)เ+n-L/ฒ1n9Ž=0@ @ชJบKžP^Zb2bคf’hŠhธโ  !!H’ฮ@xฎไDržศ๐>b†ชฬ์ ,LlŒชศๆ:VrŽจย๖*D\tŒคบะๆ(>RfzŽขถส๎.>N\ht~†ค ! #" "!ง !  "ฆ !  ฉ !  ฅ !  %!จ !  %!ฃ ! ! j$  $ ๅ"!  $! !#๖ ! !" ๕ ! !" !#ฑ  "!่  !๔ ! !"ข ! ! ๓ ! !‹  !! !i$  $ ป! "!!ไ"! ็"! $ $$๒ ! ผ  " #k $  "ˆ  ! ! l $ $ใ  !&ุ   | $ "$ !! &f  ! ""๏ ! !๎ "![  !ื  ๆ "! %ู  !   ! !! ๑ !h $ ข  ฐ  ๐  !Œ "!J !!!j ! !๋  +  !ฺ  ๊  ๗ "!$Š  !!์ -# !š "!ฒ#ฏ %ํ_&บ "!‰" !้,"g ฎ‚7pœฤ์8\€ขยโ"Bb‚ ผุ๔(B\vชฤ๘,F`z”ฎฦ๖ "8Nbvˆšฌพฮ่๔!!&  "ใ"!! !ๅ"!! u $!ไ"!!  ้! " '!๛ "!ๆ! "!พ"  ! #!็! ƒ"!! "แ!!& "$๛ ่! $  !! " " !!& !  — !     – ! • ! ” ! “ ! ’ ! ‘ ! บ " !˜  ฝ     !&฿ !!& "  !  "!Ž "   !เ !!& !  !   " !" " ! โ "!!ณ  #๚" |"  " ๘"™" "ด๙Vฎ8bŠฒฺ$HjŠจฦไ >\z˜ดะ์$@\x’ฌฦเ๚.F^vŽฆพึ๎6LbxŽคบฮโ๖ 2FXj|Ž ฒฤึๆ๖ .<HT`lx‚Œ– ชฆ! ! "! % "! ! ฌ   "!“! " "!Ÿ! " !ช! "  W! $  ! " $ช! "ˆ  N" $  !&.  !ฃ $ Y ! !ม & v ! !ฎ  ฅ ! !p # !ธ !!  !‡  !— !! ฏ  ‚  #ฐ  !!˜ !! Q " "œ  !ถ !! &L " o # !  ‰  !#  !!ก ! ž    ’ ! "ท !! "!P " ""ฑ  " !    ภ  ƒ  !ข   M " ›  !€  …  ญ  ง     %› &!O "   ‡!! ฃ'ซ ช  จ  ฌ Z"!~ ค†!mฟ น!šK"ต nq ฉ„!}ฝX !/`†ฌา๘@b„ฆฦๆ&Fd‚ พ๚0H`xจพิ๊*>Rdt„”ขฐพฬึเ่ต !  !" … !  !" !+! ! !*! $&)! "$&ถ !  !" … $  *! !(! &(! !,! $%! ]# \ ! $$ Z! "'! ๋# &! " † !  !"น! #† # !ี ! $ฆ "! [  !   !ฅ "! B  $ ! „ # "ˆ  ค ! ๊ ! " ‡ # !„   "    ณ! ์# ‰ฒ'พบ! ! ^ ํ#>n–ธฺ๚:Xv’ฎสโ๚*@Vl‚–ชผฮ๊๖ ! ! !ฑ &!ท% " " ถ% " " ˆ#! #น% " " ธ% " " ท% "  ถ% "  บ % " '\ ! !)  "!r %   %! ! s % ด " &‰ #! " &“ #! !a # !!Š #! !/ "' %! ต% "0%! %ŠŒ! #!(ด!Zถ๎&T‚ฌึ*Rzขส๐<b†ชฮ๒8Zzšธึ๔0Nlˆคภ๘0Lf€šดฮ่4Ld|”ฌฤ๔  6LbxŒ ดศ๐   & 8 J \ n € ’ ค ถ ศ ุ ๆ ๔    $ .-! !%!!  ! ,! !%!!  ! #! !  "! !  &!  ! "!+! !!"'!  ! "!  " !&%!  !  !ว! ! " k "  !   !l ! !  !w!" &  !ฬ! !! !!!  !%!!  !.! "!  .! "#! "!#! u"  %!ฦ!   $•! !ย! !x!" #อ! #!ร  !‹ ล ! " R  $ะ ! $$ ! !V  ษ !    ! !C !  ) ! "!  !  ! ศ ! ( ! !     1  ! $ห ! ป !  !œ !* ! 'U  ” !  ! 2  ! $S  "t " a $ ส ! ฯ   "!` $ €  ! ะ   / "! t  h  $b $ g  #ย! ม! ภ! ฟ! ฝ! ผ! ป! ั  #!ฤ! ร! ฝ$-!Tพ!‹"ฮาผ,H`xŽคธฬ๐ 0@LXdp|†๏  ! %ฤ  " ต ! !๓   " $ภ " ๎  %ี ำ!ึ ! พ !"_๐ #3 ิฟ"ฬ!—0Rrฌฦเ๘"4DTdt„”ขฎบฤฮุ! #" !&" " ! 5  "!&Ž  "!a  ! ู  !D !ฺ $ !&` !ล! !"4ื ุ .! !! ’!!],\‚ฆส๎4Tt’ฎสไ0<HT^˜!!  %  ! !2 ! # #!   !! # 4 ! #!!$#!  จ! !! 3 ! !1 ! !$ #!  Œ ! %!"  "!6  ! !0  ! !5  ! #"!ฟ%!  Fn–ผเ&Fd€šฌe& $ !d& $ !c& $ $g&  f& $ "h&  w& !j& !"l& #i & k & !b&ฦ!O ะ$Jn’ถฺ Bd†ฆฤโ<Zv’ฎสๆ:VpŠคพุ๒ $<Tl„œดฬไ๚&<Rh|คธฬเ๔.@P`p€ ฎผสึโ๎๘ 7 # !> "& #; & !๒! H  ! :  !!๑!  ž!" "!n! N!  !&3 !!!I ! J  !"!I ! 6  $ 3 !#!&L <  !p! $$ D &9   !C    ?  &= & " < &  ม ! !&1  !&  "! ! ,  "!J  0 & ึ $ !&A  # E   ! F  ''8  ; & แ  #=  !B  E  จ  $!ง  !&@  5  o ! M  !%G  &K  4  2  !  ! ้  !$+  ่  ฿  Ž # เ '"-&!&็ ๆ ๅ ซ# !#ใ /& ("m!๊"ไ %โ ภ6) !*'Pvšพเ$Dd‚ ผึ๐ ":Pf|’จผะไ๘ ,<JVblv€Š’š." %๋ ! “  "!<& &  ๓"!! "=& & " H!'! ป"! !์# !;& & ั # ! %ฉ  "!8  ! # !P & !m "  #w  '7  !&ํ "  !ย "ฉ !&x  #!9  !A & ’  ร 8 9 ‘ q&r" :#ยมv"OŒ 76`Šฐิ๖4RnŠฆย๚.H`vŠœฌบศาศ!! " ว!! !#ล!! ฦ!! ว!! ๐#! Ž!$ ?! ! !ณ  &!ฤ !! ฒ !$ ๑ #! %!๗ $ $๏ !" > $   ๕ !$ $๔ !$ ๘ $ $! Q #!ำ %! $/! ] ” ๎!"y!๖.Ld|0  $ ฌ #1   $™  2  $!ช!&WฐโBrขะ&Nvžฦ์8^„จฬ๐8Z|žภเ @^|šธึ๔0Nlˆคภ๘0Lf€šดฮ่2H^tŠ ดศฺ์  " 4 D T d t ‚  ž ช ถ ย ฮ ฺ ๆ ๒ ! ' ! !"! ' ! ! ' !  "!! 8 # !Ÿ!"  !" !6 !! B"   "!E&  "!A"   ๗ $ ๕ '!?$ !! $D&  น ! "!o& !  =!"  !"7 ! !!^   ๖ #!–! $ ๔   &D&  8 # 6 !! ๔  &5  ฃ   $ฆ !"ญ  #ฅ ฬ "!ฯ  "!<  $;   ค ง ! ! !"  ฌ! $ท !& 9  !!&– &  $s  v  i & ! &๛ ! &‘   !”  " “   ’  t  ๚ ! F & ! ! ! !๙ ! "… $ "!C & $:  ฮ   N   ส !& ธ  !u R !!ะ" ๓ณ !3#$4&!:" ๒!!>&า"หอ @!@" ๘!ฎ$S•ษ ัวซ! <d„คภุ—" !! ˜" !! p"" & %!B" !  $A "& "H "" " G"",Zˆดเ 4X|žพ8TpŠคพึ๎2FZn‚”ฆถฦึๆ๔,:HT`j "!! "ฦ#  "!!!   $ถ#  ! "!! q#  ""ญ # # "!! U !"! " "!! ฎ" #  &E! ล # J ! ! ศ  #ท  !  !! !™  #"T ! #$ !! !`  !"!L ! ศ & d & c & ด # "e& ญ F !C!y ฏ$K!ษ!ฤ#w"!DM&ว&IGรu์D|ฎเDvฆึ2`Žผ๊Bnšฦ๒Fpšฤ์<dŠฐิ๘@dˆฌะ๒6Vv–ถึ๔  0 N l ˆ ค ภ ๘  0 L h „   ผ ึ ๐ $ < R h ~ ” ช ภ ึ ์   . D X l € ” จ ผ ะ โ ๔   * : J Z j z Š ˜ ฆ ด ย ฮ ฺ ๆ ๒ $.8@+ " !  !!  ู!! #&  ! !! & ! &S!! "!  !R!! "! !ฺ!! #& ! ~"!& $ !}"!& $ !L!! "  ! "  Q!! "! !!& &  ›!& "!   $   ึ!!  ! U!! "! #K!! ! œ!& "! !! "  ž!& & !!๙    " "W!  !&๛  !!๚  !!g$ #!  " O!! ! T!! "! ุ!! ! M!! "!!d"#  "! $  ซ!  P!! !!ื!! "!V!! # ธ ! r$ ! #h& ! "! !O!!  &g$ #! h& ! "! !#<!!"  !!]  ! $ Y!! !–  !T"" "X!! $W!! Z!! # $! _  ! }  "^ ! & ส   œ & ! "!^  !P  #" n " ! N !! š &  š   $! #› & แ ! "!ช  แ ! ` ˜  e $ 'ษ  { !ห !   !Ÿ $  b !!#$ !!ผ $ !F ! !%!S "!! เ !   $f $ #!O  %!;  V !!y"!&z \ !$H"J!! c"!N"• |x!ำQ'Ic"$&a! ิ!&b!อ!฿![ฬ™&ส#R!— ห-\ฒะ๎ (D`|˜ฒสโ๚&:NbvŠžฒฤึ่๚ ,<L\jv‚Žšคฎธยสาf  $!  !!u "!n" " $! j " !r   ž  ! #x "  !t  $u  !   &v  "ฮ %! !G %!  l " $€ ! ก "          w " s  !m" "!&k!   ฐ%!" %!  Y  X~!I! dp&& e"qoi‚#&>R`j{  z   า #ฑ!ฯ %Lxคส์.Lh„ ผึ๐ 8Ph€˜ฎฤฺ๐2FZn‚–จบฬ>!  !=!  !!?!  !U !$ !‡  !† !&  $ U  &| $ " y  "  $ !W  ƒ $ !} $ "{   $ "z $ X " $Y " "!…  !&{ $ &z     ! $ &‚ $ "!Z " Ÿ !„ $ $€ $  !ก [ " "!!~$ &Vก ข &0Tt”ฒฮ๊*>RfxŠšชบสึเ๊๒ˆ!    !‡! !  !!   !‰ ! !#ฒ ! !s   !๗ @  !%!    "&ี! ! "&๘!  "!ิ*ƒ!Šฐ\ฏ‹"!"  "4_  "! ‘ "! $ &'ZoneMinder-1.32.2/web/fonts/glyphicons-halflings-regular.ttf0000644000000000000000000013053413365153155022574 0ustar rootroot€pFFTMm*—GDEFD OS/2gนk‰8`cmapฺญใ˜rcvt (๘ gaspglyf}]ยo”คheadM/ุœผ6hhea Dœ๔$hmtxาว `tlocao๛•ฮ Œ0maxpjุขผ nameณ, ›ขขpostบฃๅ5ฆ€ ัwebfรTPฑTฬ=ขฯะvuะvs—‹ ะZ ะค2ธUKWN@ ภ{ , h, ฐh@( + ฅ / _ ฌ ฝ"#%&&๚' 'เเ เเ)เ9เIเYเ`เiเyเ‰เ—แ แแ)แ9แFแIแYแiแyแ‰แ•แ™โโ โโโ!โ'โ9โIโYโ`๘ * ฅ / _ ฌ ฝ"#%&&๚' 'เเเเ เ0เ@เPเ`เbเpเ€เแแแ แ0แ@แHแPแ`แpแ€แแ—โโ โโโ!โ#โ0โ@โPโ`๘ใฺfbเ฿ไ฿ต฿i฿Yฺฺู!ูู     ๘๗๑๋ๅ|vpjdc]WQKEDึีฮอลฟนณ Œ5 *+  ฅฅ  / / _ _ ฌ ฌ ฝ ฝ""##%%&&&๚&๚' ' ''เเเเ !เเ&เ เ)0เ0เ9:เ@เIDเPเYNเ`เ`XเbเiYเpเyaเ€เ‰kเเ—uแแ }แแ†แ แ)แ0แ9šแ@แFคแHแIซแPแYญแ`แiทแpแyมแ€แ‰หแแ•ีแ—แ™โโโ โ ๅโโๆโโํโ!โ!๏โ#โ'๐โ0โ9๕โ@โIโPโY โ`โ`๘๘๕๕๖ช๖ช (๘(h .ฑ/<ฒํ2ฑ<ฒํ2ฑ/<ฒํ2ฒ<ฒํ23!%3#(@่๐๐ เ(ะddLL[27>32+&/#"&/.=/&6?#"&'&546?>;'.?654676X& žjžเ  เžjž )"& žjžเ  เžjž )L เžjž )"& žjžเ  เžjž )"& žjžเ LL#32!2#!+"&5!"&=463!46ยศ^ขศข^Lขศข^ศ^p@LE32!2+!2++"&=!"&?>;5!"&?>;&'&6;22?69๚ ” x } x } ” ํ x }ํ x v” ๚คคL ”   d   ฎ  ฎ   d   l คคdŒฎ;2#4.#"!!!!32>53#"'.'#7367#73>76่๒p<ต#4@9+820{dิ–dิ 09B49@4#ฎbkงฮv$B ูdpฺd†>uฎฝhi-K0! .O2d22dJtB+"0J+ซkuž0ชwd/5dW…%{Lฐ>G!2+!2++"&=!"&?>;5!"&?>;4632654&#ฌ^CjB0  0BjCฒ x Š • ๕ x u๕ x uถห@--@ฐ$?2O*$ $*P2@%d   ฏ  ฏ   d    ศิBVT@ศLผ!2#!"&=46๚ เผศศศ่ฐ%A+32!546;5467.=#"&=!54&'.467>=่2cQQc2เ2cQQc2ศA7 7AA7 7Aฐ–d[•##•[––––[•##•[d–ศd76!'๖ ˆำ๛Pิ‡ $ op zyถรณ#ป%**%๊ท$ ”–pdฐL #7!2"'&6&546 6'&4#!"&7622?62~ ฬฬ๘ Œ ๘\l ๛‚ lคคL ษ7 ฺ๘๘ & ๐ ฺ €” lคค๐๐บบ 2'7' เ&™ cึ_"™ึfณnบ ™&\ึ`tšึfjpOฐฐ32!546;!ผ๚เ๚ ฐŠฺ22&&Lœ%6.676.67646p…'0SFOˆ$WOHBจXAOˆ$WOHB๙ฃ"ม7Q)mr ›ข*`)nq&* ๘ปง)2"'#'".4>"2>4&ศถƒNN;)ํwŽdถƒNNƒrฐ”VV”ฐ”VVงNƒถdy๎%:MNƒถศถƒ[V”ฐ”VV”ฐ”dXฏD>.54>‰0{xuX6Cy„จ>>ง…xC8ZvxyตDH-Sv@9y€ฒUUฒ€y9@vS-Hำ^{”62!2'%&7%&63—ƒฅช‚ฉช‚ฅ aŸ ๙o ๖๗ ๛ ำ^{”"62!2'%&7%&63#7'7#'—ƒฅช‚ฉช‚ฅ๓๐ลJมรJภ๊N aŸ ๙o ๖๗ ๛ dŽโ‹ŒๅŒำฐฐ&2##!"&=467%>="&=46X|ฐ>& f  ๛‚  f &>ฐฐฐ|๚.hK ๆ ]  ] ๆ Kh.๚|ฐ ฐL#'+/37GKOSW!2#!"&54635)"3!2654&33535!3535!35!"3!2654&35!3535!35~  ๛‚ Udฃฺ  & sdd dd dแฺ  & ๏d dd dL ๛ๆ   ddd ข  ^ ddศddddศddddd ข  ^ dddddศddddLL/?!2#!"&546)2#!"&546!2#!"&546)2#!"&5462pmpฝpmpLppจpp LL/?O_o32+"&=46!32+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=462ศศฅศศฅศศ๕ศศฅศศฅศศ๕ศศฅศศฅศศLศศศศศศpศศศศศศpศศศศศศฐL/?O_32+"&=46)2#!"&=4632+"&=46)2#!"&=4632+"&=46)2#!"&=462ศศฅผD…ศศฅผD…ศศฅผDLศศศศpศศศศpศศศศ&,่  62"'&4?622ั;ฑ๑€ฑซ;ฑ๐€ฑซnnBB# "' "/&47 &4?62 62ˆฒ๕ ฒ๔๔ฒ ๕ฒ  ;ณ๔๔ฒ ๕ฒ  ฒ๕ ๋ล™%I2"'#".4>"2>4&3232++"&=#"&=46;546™ฤณ‚MN,mิwŽbดMMoณ˜XX˜ณ™XXผ– K  K – K  K™M‚ณbyีl+MMดฤณ‚MX™ณ˜XX˜ณ™# K – K  K – K ๋ล™%52"'#".4>"2>4&!2#!"&=46™ฤณ‚MN,mิwŽbดMMoณ˜XX˜ณ™XXX^  ข ™M‚ณbyีl+MMดฤณ‚MX™ณ˜XX˜ณ™‡ –  – ™ฐ-32+"&5465".5472>54&&ddงา[›ึ๊ึ›[างg|rล่ลr|ฐpแฆ>ูธuึ›[[›ึuธ'>ฆ7ศxtลrrลtxศdฐฐ/?32+"&54632+"&54632+"&54632+"&=46–  – –  – –  – –  – ฐ ๛‚  ~ p   ๎ ิ >  ย ศ ๚  ๚ ––GO27'#"/&/&'7'&/&54?6?'6776?6"264X!)&1-†=+P˜˜P08†,2&+!)&1-†<,P —— P/:…-1&+xฒ~~ฒ~–˜P09†,1&+"(&1,†=,Q——Q09†-0&* !(&0-†=,P˜™~ฑ~~ฑdฐ!%)-1!2!2!5463!546!5#!"&53333333๔,); ๛ด ;),,;)D);dddddddd;)d KK d);dddิเ);;) dDผDผDผDผ ั62++"&5!+"&5#"&l` ฏ ๚ ิ ๚ ฏ j`  ม  w‰  ? d่ฐ3!#!"&5463#"&=X;),เR๚ฐp);vLp๚™™02".4>"2>4&3232+"&546ใ๊ึ›[[›ึ๊ึ›[[›ฟ่ลrrล่ลrr|2 ฏ  ๚ ™[›ึ๊ึ›[[›ึ๊ึ›;rล่ลrrล่ล  ํ 2  ^ œฐ )#!3333ๆ)๒)ๆฏัขะžเpฐิ,pิ,dฐฐ/3232"'&6;4632#!"&546;2!546&ศฟ น & น ฟT2  ๛ๆ  2 „ฐ>pยเ ข  ^  ฏฏ ™™12".4>"2>4&3232"'&6;46ใ๊ึ›[[›ึ๊ึ›[[›ฟ่ลrrล่ลrr|– ‰ ฿ & ฿ ‰™[›ึ๊ึ›[[›ึ๊ึ›;rล่ลrrล่ล  ํํ ™™12".4>"2>4&%++"&5#"&762ใ๊ึ›[[›ึ๊ึ›[[›ฟ่ลrrล่ลrr็฿ ‰ – ‰ ฿ &™[›ึ๊ึ›[[›ึ๊ึ›;rล่ลrrล่ลํํ  ฐฐ9!2#!"&'&547>!";2;26?>;26'.แ๎ บ ๛ด น—ิ W – & ๚ & – Wฐ tW  ฉŒ ศ >  ˜  ˜  ย ™™'2".4>"2>4&&546ใ๊ึ›[[›ึ๊ึ›[[›ฟ่ลrrล่ลrr‹™[›ึ๊ึ›[[›ึ๊ึ›;rล่ลrrล่ล]พ $ พ  ™ฐ(76#!"&?&#"2>53".4>32ณ‡  – …mtลrrล่ลr–[›ึ๊ึ›[[›ึuภ$‡ –  …Lrล่ลrrลtuึ›[[›ึ๊ึ›[™ฐ576#!"&?&#"#4>323#"'&5463!232>ณ‡  — †ntลr–[›ึuภ๋–[›ึuภœ†  h …n‚tลr$‡ —  †Krลtuึ›[ฟuึ›[v† h  …Lrล dฐฐ/?O_oŸ!2#!"&546!"3!2654&32+"&=463!2#!"&=4632+"&=463!2#!"&=4632+"&=463!2#!"&=4632+"&=463!2#!"&=46}  ๛ๆ ภฎ  R 2  2 าย  > พ2  2 าย  > พ2  2 าย  > พ2  2 าย  > ฐ ๛‚  ~ ศ ฎ  R d 2  2  2  2 ศ 2  2  2  2 ศ 2  2  2  2 ศ 2  2  2  2 Lฐ#54&#!"#"3!2654&#!546;2„uSิRvd);;)„);;)จศ ศSuvRศ;)จ);;)X);––dLL 732#462#".'.#"#"'&5>763276}2 dภ!C@1?*'),GUKx;(.9)-EgPL ๛อ3 0[;P$ 9ถ7W Wฐ—!1A2+"&54. +"&54>32+"&546!32+"&546ไ่ฃc 2 ไ๚ไ 2 cฃ*    `    —cฃtิ  ,ัrrัิ  ,tฃภ 4 ฬ 4 ฬ ำG9%6'%&+"&546;2762"/"/&4?'&4?62A ๚  ๚Xx"xx"xx"ww".ฌ  ฌ ^ „x"xx"ww"xx"ำr/%6'%&+"&546;2%3"/.7654'&6?6A ๚  ๚า `Z  HN.ฌ  ฌ ^ d กš g~„jฤb์1K3#"/.7654&'&6?6%6'%&+"&546;2%3"/.7654'&6?6ว ‡D@  *o;7 *‚ ๚  ๚า `Z  HN์ ณูiหT "–ฒZฌG !พฌ  ฌ ^ d กš g~„j ฐฐ !%-;?CGKO3#!#!#3!##5!!!!#53#533!3533##5#535#5!!#53#53#53!5!๔ddpd๔ผศศิศdX๛ด,,เddผddDศ ศd๔ศdศddศ,D,ddd„dd dd๔ิ,„dddX dศ,,ศdิ,ิ,ศddd ๔dิddddศdศิ,ศdddิddd ฐฐ #7#3#3#3#3#3!5!#53#53#53dddศddศศศdd,ศศเิ,ศddศdd,ศศศ่่่่่๛Pdd[[[[[ ฆฐ  "'463&"26๔ช0V C;S;;S;ฐV0ช อ;;T;; าฐ ! "'463!"/ &"26๔ช0V ช08จDำ;S;;S;ฐV0ช V08ชผอ;;T;;dฐฐ&!2&54&#!"3!2#!"&54?6,9K@  D@ ผ  ฎ ‹ฐK|@ ถ @  J  ฯ‹ศLฐ !2 46๚ >>ฐ๛‚ผCฐฐEU!"3!26?6'.#"#!"&/.+";26=463!2;2654&!"3!26/.6D N9 ย >SV– N ฺ N –  –  ๎  – ฑ & X &ฐ ำl l- p œ œ  v  –  –  Š จ ˜  ˜ dฐL!)13232#!"&546;>35"264$2"&4๔ศ8]4$–);;));;)– '3]ศdฯพ‡‡พ‡๏V<?!ฉ(% _5,Ry:" *2“8 ฌTขฏ2*BBW-‘Y". BB % ๎Zษd๐ฐ'2;#!5>54.'52%32654.+32654&+๑50;*7Xml0 ); !ื9uc>--‹Ni*S>vุPR}^Ÿ3:R.CuN7Y3(;  G)IsC3[:+ 1aJ);4ฎePZศoฐ!56764.'&'5mSB„ ,J บญ  ฐ95(น1(aaR@ 9ตฐ%/#4.+!52>5#"#!#3'3#72 &ศ2p"ศ& 2่๛›KK}}KK}„ ฎdd R ,ศเงง ง!ตฐ%/#4.+!52>5#"#!5!'7!5L2 &ศ2p"ศ& 2่Cงเงง „ vdd Š ,๛‚}KK}}KKฐL/?!2#!"&=46!2#!"&=46!2#!"&=46!2#!"&=462Xจ่ เL๛ดLddิddิddิddฐL/?!2#!"&=46!2#!"&=46!2#!"&=46!2#!"&=46๚ผDณL๛ดผDณL๛ดLddิddิddิddฐL/?5463!2#!"&5463!2#!"&5463!2#!"&5463!2#!"&๔Xจp่ศ เิL๛ดถdd้dd้dd้ddฐL/?!2#!"&=46!2#!"&=46!2#!"&=46!2#!"&=462L๛ดL๛ดL๛ดL๛ดLddิddิddิddฐL/?O_o32+"&=46)2#!"&=4632+"&=46)2#!"&=4632+"&=46)2#!"&=4632+"&=46)2#!"&=462ddA เ้ddA เ้ddA เ้ddA เLddddิddddิddddิddddœฐL#*:J!#;2+"&=46!2#!"&=465#535!2#!"&=46!2#!"&=46dd–dd๔ ๚งศศย,ิXจLddิdd๚}KdKฏddิddL#*:J32+"&=46#3!2#!"&=463#'7!2#!"&=46!2#!"&=462ddgddฎ๔ /ศศงง๛ๆ,ิXจLdd๛ดLิddศdK}}ฏddิddศฐ่!2#!"&546 K๎,,,,„ิ,่,v,,Š,D,,ฐL!2#!"&5467'2"&4,X๛จJ๗*J%์าpNNpNL ๔dถƒœ>เ๔tNoOOo6‘2.'&54>"264ˆuฦsFEฒ66 !^Xm)!fh˜H„uXฃyHร‚ธธ™™2".4>"ใ๊ึ›[[›ึ๊ึ›[[›Ktลrrล™[›ึ๊ึ›[[›ึ๊ึ›oVrล่ลru฿5.54>6?6&'.'&76#&*IOWN>%3Vp}?T›|J$?LWPIผ)(!1 )  Huwsu‡EG€^F&:c—YE‡vsxv!K‚:%A'# " A)Yถ หl */7>%!2!"3!26=7#!"&546 7่l l27ป);;)๔);ศปฃpฅนน8–ก7c๕s* sศ ป;) );;)ถศดฅนนฅฅนื–2ชc“L6!#"3!2657#!"&546&'5&>75>^iค4ร);;)๔);ศนฅpฅนนSฌ 9dTX .9I@F* L’6;) );;)™gฅนนฅฅนำฮ ห 0!;bA4า L5!2!"3!26=7#!"&546 62"/&4?622^^ ชศ ๘ ศช ๘ ฏศ ๘ ศฏ –๘ ศญ ๗ ญศ ๘ ศญ   ญศ ศ„L326'+"&546๚dะ0dLJลลJ่ฐL#3266''+"&5462dะะ00dLJลJลลJลJ่ฐ3''&4766ฐ0์ะลJเ*เJลศ36 &546๓.า2   ่ศd„่32+"&546!32+"&546๚ศศฅศศ่เ เ ศdL่#!"&5463!2Lเ ถเ 346&5&546๔์0dถ * ถ;่ณO#72#"&5&5&5464646dd12าาNต: ต9 ่ >ถ =ถ,่L32+"&5&54646Rdd0ะLถ;่;ถdศฐH  #!"&762!2#!"&=46ฎ๕ ๛๎ ๕*๗่9ไHddˆuJ  u `ลุ(„ŸŸฦ(&;(J ' 7(ูฦaŸฦ#ูฦaaฦ™™32".4>#"#";;26=326=4&+54&ใ๊ึ›[[›ึ๊ึ›[[›}d––d––™[›ึ๊ึ›[[›ึ๊ึ›บ–d––d–™™2".4>!"3!26=4&ใ๊ึ›[[›ึ๊ึ›[[›E ๔™[›ึ๊ึ›[[›ึ๊ึ›~dd™™32".4>"'&"2?2?64/764/ใ๊ึ›[[›ึ๊ึ›[[›ๅ xx   xx   xx   xx ™[›ึ๊ึ›[[›ึ๊ึ›ญ xx   xx   xx   xx  ™™$2".4>'&"2764/&"ใ๊ึ›[[›ึ๊ึ›[[›T‹๒w‹™[›ึ๊ึ›[[›ึ๊ึ›1U‹๑w‹™™;K2".4>";7>32";2>54.#";26=4&ใ๊ึ›[[›ึ๊ึ›[[›?2".4>#";26=4&#";#"3!26=4&+4&ใ๊ึ›[[›ึ๊ึ›[[›––  –  ๚  KK  ^  K™[›ึ๊ึ›[[›ึ๊ึ›V –  – ิ 2 ศ 2  2  ฐฐ/_3232++"&=.'#"&=46;>7546+"&=32+546;2>7#"&=46;. – g—ย  ย—g – g—ย  ย—gน – Dfจ  จfD – Dfจ  จfฐ ย—g – g—ย  ย—g – g—ย อจ  จfD – Dfจ  จfD – Df™™?2".4>"2>4&"/"/&4?'&4?62762ใ๊ึ›[[›ึ๊ึ›[[›ฟ่ลrrล่ลrrš@||@||@||@||™[›ึ๊ึ›[[›ึ๊ึ›;rล่ลrrล่ลZ@||@||@||@||™™02".4>"2>4&"/&4?62762ใ๊ึ›[[›ึ๊ึ›[[›ฟ่ลrrล่ลrrƒjภสjOล™[›ึ๊ึ›[[›ึ๊ึ›;rล่ลrrล่ล}jฟหjOล™™!2".4>"&32>54ใ๊ึ›[[›ึ๊ึ›[[›KtลrAKi๕ธhstลr™[›ึ๊ึ›[[›ึ๊ึ›;rลtxiKAะธ>rลtsSฐ๙6!2#!'&4' &ฺ๖Fซ ศ ซ &S™๙ &5!"&=463!46 ๖ฺ&๑U & U #ศ# ท]™ #!+"&5!"&762ชซ ศ ซ &‰๖ฺ& ท]ฐ32!2"'&63!46&ศ# U & U #ฐฺ๖ &ท™] &5>746 ๖^ฐฅ$,[‡ว~UU & U #$DuMiฑqF ฐฐ+!2/"/&4?'&6!"&546762R,^๙j๙^!ิ^๙j๙^ฐิ^๙j๙^๛P,^๙j๙^IIgg+#!"&546762!2/"/&4?'&6๖j๙^ิ^๙ ,^๙j๙^`j๙^,^๙๑ิ^๙j๙^™™/2".4>#";2676&#";26=4&ใ๊ึ›[[›ึ๊ึ›[[›ณะ:#6#:1–  – ™[›ึ๊ึ›[[›ึ๊ึ›บา.  –  – ฐฐIUaho276?67632;2+"!#!54&+"&=46;2654?67>;26/.'&;26!"&5)#! ฤ &ฤ0  =  2 pศp 2  =  ฆ 35ฑ3 ็ ‰ X ‰ฐ v  v !{,  2  ฏ,ิฏ  2 0€yข  • ช   r w‰  ฏฆ+I6.'&&&547>7>'.>7>&67>7>7>-Bla‹bŽD8=3™*U  :1'Ra\‡{ภ%&ข=>8\tYR-!Šq[Fak[)ฆฒศ•X1 ™"@&J<7_…?3J5%#/D &/q!!6ROg58<'([@1%@_U2]rฯO.>7'&767>.'&'.'&>77>.'&>' '8GB    `ŠH  >JS>H7 '+" NA 5M[`/Pg!;('2"&"IbYฯC€e\D9$ 886#1%)*ƒ‘ง—J7gG:    8G\au9hชoK$œ]54<&"&5476&2>76&'&6?6&'&'.ค{nO9:On{ข{nO:9On{ฐ๘ฐFZ  2Z_ƒˆƒ_Z2  Z# %8-#,- "F-I\b\I*I\b\I--I\b\I*I\b\I9>|ฐฐ|;7Es1$F^D10E^E$1u$/D0 "%,Iิฐ';L!#7.54>327377>76&'&%7.5476&6?'&'.P”[ฉvY,9On{R=A ”&/l‰'Pj˜R.Mv&  6QFZ  *HLh5)k|# %8- ,- "xatzbI\b\I-yRตUึ4Zrncญ1ˆ?1FrEs1ญป1) ๙๙]@ €€ @]๙ )1ES>Lฐ'+/37;?CGKOSW[_c3232!546;546;2!546#!"&5353353353353353533533533533535335335335335Rd2๛ด2d๔dddddddddd|ddddddddd|dddddddddฐ2––222p๎ศddddddddddศddddddddddศddddddddddw—%7&=#!"&=46;3546'#"&=463!&=#'73546o๙๙ŸจัX๑ฉzั#๙๙๑ตzŸๆ*ๆ–จdX–˜zdMๆ*ๆ–ตz–ฐL!2#!#"&546d่);;)ฌะd);;L;)จ);ิ,;)X);dLฐ ?32!546!32!546".5!2>&54=–ศิัศิ(LfœภœfL(, '6B6'ฐ๚๚๚๚pิ)IjV\>((>\VjI),๚ +'%! !%'*๚ิLย 'LฦŸŸล'›วaŸว'๎M 7 Mฺูฦaaู'วŸaQd_่)!232"/&6;!%+!!"&5#"&?62”๊–ๆ*ๆ–ƒๆ–ื–ๆ*ู่๙๙‘ธ๙pศ&๙žฐ032!2#!!2+"&=!"&=#"&/#"&468^&€d,!‡02*ิ*ษ6ฐข%%+ศ*2222 ม*ฐL !53463!2!!ฐ๛Pศ;),);Dฐ๛P่dd);;)ศเL 3463!2!!ศศศ;),*:๔,ิ๛P, pX);;)ศdDผEkฏ+32"/&6;#"&?62{ๆ––ๆ*ๆ––ๆ* ๙ง๙๙Y๙Dฏk&=!/&4?6!546จ๙๙จ๙๙X`ๅ)ๅ —— ๅ)ๅ •• ฐฐ !.#!"!"3!26=4&53353‘ฌ$`$ฌ-);;)่);;ซddd-(d;)d);;)d);ศddddœdฐL #12"&54%##"+"&'=454>;%".=4>7i**d]&/T7 ศฃ" LฎR™ศ์Q๚ ๚ิ๚ )2( Jfฐ,53232#"./.46;7>7'&6327"&)^Sz?vdjŸO9t\U>/ v?zS$2451 7F8ฐ%Mนญ)(  ()ญGM~ ๛พ1==œœ7'''7'7'7'77 เN๊ดด๊Nเ-››-เN๊ดด๊Nเ-››ฒNเ-››-เN๊ดด๊Nเ-››-เN๊ดดdฐฐ!-=32!2+"&/#"&54?>335!7532+"&546Š2(<H(<๎,๚F=-7‘` 1d–dˆึ๚>2vddฐQ,–}Q,d-จ!2$'$ฤฦ(dิี‰dw}แฏิ ๔ฐL 0<32#!+"&/&546;632+"&546!#35'!5X๚,๎<(ธ<(21 `‘7-=|้dd_ˆd–d22ย๚L!จ-d,Qv–,Q(ฦฤ$'$dd ๔d‰ีิฏแ}wdO7G%6!2+#!"&5467!>;26&#!*.'&?'32+"&546dkn  T.TlnTjƒชฆ:d%ƒห8   ’VOddiๆp &yLNญ(ข  % H YS(22ทSไ ๔œdฐO6F#!"&'#"&463!'&6?6*#!32!7%32+"&546n ฌชƒjUmlT.U  nJ’   %‚&jชPddOๆ ๓ ข(SNLy& pฎทd(ญY๗์ไ ๔aL7G2#!"&/&?>454&/!7%.!2#!"&=46NS(ข ๓% ๆp &yฦ22ทS์๗Yญ(–๔ nTjƒชฆkn  T.Tึห8   ’Vญd%ƒ dd่-I!26=4&#!""&5&/&7>3!2766=467%'^๔ NLy& pๆ  ข(‰์Sท22(SYLddๆjTnlT.T  nk ฆชฯญV’   8หƒ%dšš%2".4>%&!"3!7%64โ์ึ›[[›ึ์ึ›[[›†ํํ  š[›ึ์ึ›[[›ึ์ึ›9฿ ‰ – ‰ ฿ &šš%2".4> 6=!26=4&#!54&โ์ึ›[[›ึ์ึ›[[›%ํ  ํš[›ึ์ึ›[[›ึ์ึ›่฿ & ฿ ‰ – ‰ šš%2".4>&";;265326โ์ึ›[[›ึ์ึ›[[›K฿ & ฿ ‰ – ‰ š[›ึ์ึ›[[›ึ์ึ›@ํํ  šš%2".4>#"#"276&+4&โ์ึ›[[›ึ์ึ›[[›—– ‰ ฿ & ฿ ‰š[›ึ์ึ›[[›ึ์ึ›ป ํํ ˜˜–ฆ2".4>%&277>7.'.'"'&65.'6.'&767>'&>7>7&72267.'4>&'?6.'.'>72>ไ่ี›\\›ี่ี›\\›d+: =?1 " "/ ?9 #hu!$ 0 E.(,3)  (     *!A 7 ,8 !?*  ˜\›ี่ี›\\›ี่ี›  ' "r"v G  .&* r$>   #1    %  *  '"  $  g2( % ฏ…67'"/&47&6๔คŽ‘๛PM<†;ฌ+oX"O…\eŠ่~Y‡+" ฌn+Weษ`ฐฌ#'7;!2#!"&=46#3!2#!"&=46!!!2#!"&=46!!d่);;));;ศศ่);;));; ๔่);;));;ิ,ฌ;)d);;)d);ddศ;)d);;)d);ddศ;)d);;)d);dddLฐ !2#!"&46!–„|;ศข„ฐ**Dิศ๔๔dฐฐ%32!2!5#!463!54635#!"&=๔ศ);,); ศ ;),;)ศ๔;));ฐ;)d;)pdd);d);dddDศ);;)ศฐฐ+AW!2"/&546)2/"/&4?'&6#!"&54676276#!"&?'&4?622,^วjว^5,^วjว^/jว^ิ^วห^ิ^วjฐ^วjว^,ิ^วjว^&jว^,^วฮ^ิ^วjจจ#;CK2".4>"2>4&$2"&4$2#"'"&546?&542"&4$2"&4๒Ÿ__Ÿ๒ ^^ ฦเฟooฟเฟoo-- - L- 73H3)z ‡- - - - จ_Ÿ๒ ^^ ๒ŸWoฟเฟooฟเฟ -!!- -! ‘$33$ 1~ค - - - -ุZนผ[%676&'&#"3276'.#"&477>32#"&'&6767632'."ี[v_"A0?! ˆ-  Y7J3$$ ซ)G"#A.,= # (wn‹kV8@Fv"0DG([kPHNg8B*ญึ[eb›2!‰5(7>B3$$' ฎ)M"#!7)/c# *xn‰fL@9NพDH7!$†W]ตB$&dXฏDD>.54>"".#"2>767>54&‰0{xuX6Cy„จ>>ง…xC8Zvxy#!?2-*!')-?"CoA23:+1! "3)@ +)?jตDH-Sv@9y€ฒUUฒ€y9@vS-H-&65&&56&oM8J41<*.0(@  )*D*2Om9๒wพ.2&/7'/&477"/&4?ซปBB8"._๗{ิiBBi BBๅBบBBB7._๗…พBB^*k"5._๘{ิjBบBFi BบBๅBBBปB77/_๘…ศ่ฐ2#!"&54>!"264ชšd:;)จ);XV==V=ฐ.2G);;)น3-ชDผ=V==Vฐฐ "/''!'&462†*$้ิฬ่ห3า, #*กๆ*#๕ิาอห4ิ$*' เ2@K#.'#5&'.'3'.54>75>4.ผ&ER<,Ÿ 3'@"‹ช MOW(kVMbO/9X6FpH*M6&+ะส  4C4%df”ญJ2#4.#"3#>36327#".'>7>'#53&'.>761T™^™'<;%T)๑ล-6"b Œ"S5268 jt&'V7  0 $ฆ -$aญP‹N(?",9J0* d2‚>2 "“" ‘  7Gd/9+DAL!X—ฐ32"/&6;3+##"&?62–ๆ*ๆ–ศๆ–ศ–ๆ*,๚๚„๙|„๙่ฐ%#5##!32"/&6;3353!57#5!่ddd,จ–ๆ*ๆ–ศ‘dcศศิศศ,ผdd๔|๚๚„dศศ๚d–๚d่ฐ!%32"/&6;33!57#5!#5##!35–ๆ*ๆ–ศXศศิศศ,ddd,วd,๚๚„–๚d–๚d๛Pdd๔dศศLฐ32"/&6;3##53#5#!35–ๆ*ๆ–ศXddศddศ,วd,๚๚„ d๛PddศศLฐ32"/&6;3#5#!35##53–ๆ*ๆ–ศผdศ,วdddศ,๚๚„ ddศศ๛ดdฐฐ32"/&6;3#53!5!!5!!5!–ๆ*ๆ–ศ๔ศศdิ,dpd ๔,๚๚„ศศ ศ ศ ศฐฐ32"/&6;3!5!!5!!5!#53–ๆ*ๆ–ศ ๔dpdิ,dศศ,๚๚„ศศ ศ ศ ศLL!2#!"&546!"3!2654&^ขผปฃpฅนนg );;)๔);;Lปฃpฅนนฅฅนศ;) );;)๔);LL+!2#!"&546!"3!2654&&546^ฅนนฅpฃปผd );;)๔);;oLนฅpฅนนฅฃปศ;) );;)๔);‚พ $ พ  LL+!2#!"&546!"3!2654&!2"/&6^ฃปนฅpฅนนg );;)๔);; พ $ พ Lผขpฅนนฅฃปศ;) );;)๔);ศLL+!2#!"&546!"3!2654&#!"&?62^ฅนนฅpฃปนg );;)๔);;๛พ p พ $Lนฅpฃปผขฅนศ;) );;)๔);ฯL5!2#!"&=463!2654&#!"&=46&=#"&=46;546&ฅนนฅpย);;)>ฟDผ๚๚Lนฅpฅนd;)๔);d้ไ&ไ –ศ– ูืž#%2"+'&7>?!"'&766763 ˜,๗๓  P''า K ป ž  S#สล  ๅnnV/ำL5!2#!"3!2#!"&546&=#"&=46;546^>);;)ยpฅนน๑Dผ๚๚Ld;) );dนฅฅน้ไ&ไ –ศ– ฐฐ1!2/"/&47'&6#"3!26=7#!"&5463!๎mศ)8m๏œ);;)๔);ศปฃpฅนนฅ,ฐpmศ)8mิ;) );;)”ศึฅนนฅฅนขข#2".4>"2>4&2"&4แ๎ู]]ู๎ู]]รๆยqqยๆยqq{ rr rข]ู๎ู]]ู๎ูGqยๆยqqยๆยsr rr Lฐ#3232"'&6;46!2!54635ยศ๕ ' … ๚…่๛ดgdฐขVช^|๚๚d22Lฌ# ++"&=#"&7>!2!54635Gz ๔"ศ๚ '๙่๛ดgdžM ๚๚!ฏ๚๚d22LK" 62"'&4?62!2!54635Œq‹๓‹ิ่๛ดgdำq‹๓#‹า๚๚d22L› #'762'&476#"&?'7!2!54635‡Ž*MิMาซะšิ=่๛ดgdŽMิL*šาฉะ›ิ:๚๚d22Lฐ#'/'7'&6"/&4?!2!54635^Wะ›ิ›ารL*ŽM๚่๛ดgdฐซะšิšาPMŽ*MX๚๚d22์ฐฏ% ! ฐฦqฌ3ซgqง๙ๆนdLฐ+!#"&546;!3#53L–D–๚๔dศdd่ฎp่ิ,ศศEฐ/'&"!#"&546;!3#53"/&4?6262Lี_  •ศ–๚๔dศddฐj\สjO)่•ี_ “p่ิ,ศศฮj[หjO) ฐ>'.!#"&546;!3#53"/"/&4?'&4?62762Lg†%๖ท–๚๔dศdd๖FƒƒF)ƒƒ)FƒƒF)ƒƒ)่๓g†๖p่ิ,ศศŒF)ƒƒ)FƒƒF)ƒƒ)Fƒƒ—ฐ/!"!#"&546;!3#533232"/&6;546Lข –๚๔dศdd–d–ๆ*ๆ–่ิ–p่ิ,ศศจ๚ๆๆ๚—ฐ/'&"!#"&546;!3#53++"&=#"&?62Lฅ*๙n–๚๔dศdd๋ๆ–d–ๆ*่pฅ๗p่ิ,ศศ…ๅ๚๚ๅศฐL !2!546#!"&5!52L๛P“๛ดdL––ิฺ&ิศศ}ญ—-1;&=!5!546#"&=46;#5376!!/&4#5;2+ง๘๘p/22ศdd‚๗p๗ddd33ๆ*ๆ–ศ–…dศศหๆ–ศ–ๆ*yศddฐฐQ%6+"&5.546%2+"&5.54>323<>3234>^%ศ"%แ ศ"  d d 1t๛ฎ5gD‘ >?1) Aฟ..@ย  ข^  ข^ dฐL3"!5265!3!52>54&/5!"!4ฐ"2pK Kp"2K๔KL8 ˆ88 %Šv% 88 x88 %vŠ% 8LL  $(4!2#5'!7!!2#!"&546!55%!5#!!'!73ฃwiูศpdw%,);;)ิ);;),ผp,ผ‰d‰dศi่–bbศdศ;) );;)๔);dศศ๗…ฃ…ฦศศf๘ddศŸŸ&767>".'.7ข.‹wfw3ภฃ .1LOefx;JwF2 ๏ขย1vŽevˆ/ข 5Cc;J™|sU@ฐL#A2/.=& &=>2#!"&=46754>ธฆud?, สยส 1;ftสpR&m๛ดm&L!((" ศ""’’""ศ '$+ ไ 2ั2ิิ2/2 !ฐฐ '!'3353353!2+!7#"&46!2!546Lศจศศศศศศฎผ ‰J‰ ณL๛Pผศศ๔ศศศศเ*dd*ิ22dฐL #"!4&#"!4&!46;2ผd);,;gd);,;ิ;)d);L;)่);ิ;)Dผ);เ);;)œฐL%)!2#!"&546!#3!535#!#33ศผ|ฐฐ|D|ฐฐ„ ศศิศศ,dศศddLฐ| |ฐฐ|๔|ฐศDผศdิdd,dิd๔dิ,œฐL%)!2#!"&546!#5##3353#33ศผ|ฐฐ|D|ฐฐ„ dddddddศศddLฐ| |ฐฐ|๔|ฐศDผจศศ๔ศศdิd๔dิ,œฐL#!2#!"&546!#3!!#3!!ศผ|ฐฐ|D|ฐฐ„ ศศิ,ศศิ,Lฐ| |ฐฐ|๔|ฐศDผศิd๔dิd๔œฐL!2#!"&546!- ศผ|ฐฐ|D|ฐฐ„ ิ,ิLฐ| |ฐฐ|๔|ฐศDผ ––––,œฐL )!2#!"&546!!!#";32654&#ศผ|ฐฐ|D|ฐฐ„dDผd‚&96)‚ ‚)69&Lฐ| |ฐฐ|๔|ฐศDผจ๔dVAAT,ิTAAVœฐL%)!2#!"&546!#3!535#!##53#53ศผ|ฐฐ|D|ฐฐ„ ศศิศศ,ddศศddLฐ| |ฐฐ|๔|ฐศDผศdิdd, d dœฐL#'!2#!"&546!3!3##5335#53ศผ|ฐฐ|D|ฐฐ„DศิdXddศจd,ddLฐ| |ฐฐ|๔|ฐศDผศp๔ dศศศิdœฐL"&!2#!"&546!#575#5!##53#53ศผ|ฐฐ|D|ฐฐ„ วdวศ,ddศวddLฐ| |ฐฐ|๔|ฐศDผpศ2ศ–d d d งง%2".4>"2>4&!!!'57!เ๐ž^^ž๐ž^^žลไยqqยไยqqlิ,ิdd,ง^ž๐ž^^ž๐žLqยไยqqยไยะศddศd งง'+2".4>"2>4&#'##!35เ๐ž^^ž๐ž^^žลไยqqยไยqql2ddd–d,ศศง^ž๐ž^^ž๐žLqยไยqqยไยะd2d2ddddd๒ยA 62632+54&#!"#"&5467&54>3232"/&6;46๗nต,,.xชชx€ิPpVAbชz– ‰ ฿ & ฿ ‰Awaญ๑ญ๚๚sOEkdชbณ ํ๔๔ ๒œรA32632&"#"&5467&54>++"&5#"&76762๖nถ,+.yชxZ† % ƒ OqVAbฉๆ฿ ‰ – ‰ วAwaญxcคh“sOEkdฉc’ไํ  ฬdLm%5!33 33!#"!54&#ผ๒ช๒ชิิช๒ช๒2dd,,Mณิิd22y7›/2#"'2!54635#"&547.546324&546X^“Y{;2 iJ7-ิ-7Ji/9iJฃ›qYƒZ=gJi๛22๛iJX5Jitฃ'‰œ*BJb{"&'&7>2"3276767>/&'&"327>7>/&'&&"267"327>76&/&"327>76&/&๒oOOoSููSoOOoSูู=yฑ" $GF`   Pu "Q9   ๙cŒccŒcVQ:   Pu "GF`   yฑ" $๒oีีoSWWSo++oSWW"ฑy  `FG # ‘uP  :Q # ๚ccŒcc:Q # uP  $`FG # "ฑy  d่ฐ "!#5!!463!#53'353!"&5+ผ,ดฌ„ ?,ศdขิิขdดu „ ร „ศศ๓ ิศเิิศ  ‹ศร d่ฐ !! 463!#5##5#7!"&=)+5ผ,ขฺ ?,ศ>ขdขิช | › ๘^ฺG ิศ|ศศิd 77 Pฐ๔#3!#732!!34>3!!Šขddิขิศ!,จศd!s๐๐เ,๔ ศd,ิิ+$dขย$+pp๔LL293232#!"&=46;54652#!"'74633!265#535Šd2ิ2s);;)จ๖บ;)X>,>XดิศศL2dd2๚–;)เ);๖FD);–>XXๆิขdขdผL6=3232#!"&=46;54652#3#!"&54633!265#535ยd2ิ2s);ศศ!จ);;)X>,>XœิศศL2dd2๚–;) ิ$+;) );–>XXๆิขdขขิ  #!"&762#";2676&35’} ,๛, }@Dะ:#6#:เศญ๛ฐ&77&P'Lา. dd LL/?O_o32+"&=4632+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=4632+"&=46!32+"&=46!32+"&=46ฉ๚  ๚  ๚  ๚ š๚  ๚ ๊๚  ๚ š๚  ๚ š๚  ๚ ๊๚  ๚ š๚  ๚ š๚  ๚ L –  – ิ –  –  –  – ิ –  –  –  –  –  – ิ –  –  –  –  –  – ฐ)33#!2!&/&63!5#5353!2+!7#"&46!2!546ผdd^>1B)(()B1>^ddศ>ผ ‰JŠ ณL๛PฐศdO7„S33S„7Odศd|*dd*ิ22ฐ+52#4!!2!'&63!&54!2+!%5#"&46!2!5460P9ย<:H)"ฏZฒ" )Hฏผ–J–ณL๛P;))%&!‘‘!&•*ศศศศ*จ22ฐ$.2"&432!65463!2+!7#"&46!2!546 –jj–jท."+'ผ'+#อผ ŠJ‰ ณL๛Pj–jj–๋9:LkkL:9r*dd*ิ22ฐ,62"&5477'632!65463!2+!7#"&46!2!546X/[3oœo"oฃ"."+'ผ'+#อผ ŠJ‰ ณL๛Pk‹6NooN>Qoฃ 9:LkkL:9r*dd*ิ22ฐ",!!.54>7!2+!7#"&46!2!546X,ิ%??M๎<=BmJขผ ŠJ‰ ณL๛Pฐก‹9fQ?HSฝTTกvK~*dd*ิ22ศ่)2!546754!2#3#3#3#!"&546/R;.6p6.d6\ฌศศศศศuSpSuu;)N\6226\N)G6.dddddSuuSSudLL/3!2#!"&546!2#!"/!"&4?!"&=46!'–„|ถ  ๅเ % X๔W & เ฿ ชdDdL ๔D 2 เ % XX % เ 2 dddฐL#-7!2#4&+"#4&+"#546!2!46+"&=!+"&=ศ Sud;)๚);d;)๚);du่);๛P;๑d่dLuSศ);;));;)ศSu ;)ิ,); 2222ฉฌ  !&4762 !2!546เชƒ๛ 'YฌV/ซข |UYƒY(nช0U22!ฐ/.#!"3!26=326!546;546;33232!ฝ'p'ฝq*}จญ20ศ/2‡๚––Œ22,ิ2ฐฐ "!#!5463!#5!#!"&5463!#5„, ‰ิ w,ศจ, v  w,ศ ม O,T ิศ ม  ถ ิศœdGFV32676'&7>++"&?+"'+"&?&/.=46;67'&6;6#";26=4&ไรKŽjI C   )V=>8'"d 1*ร) "dT,Ÿ| -oหtE๚  ๚ GAkŠI ! "% ,=?W7|&๊F@Je5&2WO_e_ 2  2 œๆ~ $4<Rb%6%32!2&'&#!"&=46#";2654&'&"2647>?&/&6%?6'.'.ง. ‹+jCHf7" *:ิ>XXนP*† €@--@-˜ -?0 !3P/|)‚( )f!% = „๗ &* xศ"6ิ2&„CX>ศ>Xฌ83 Dษ-@--@‚ # ณ=I+E( /—/}X&+ 5!H d9ฐQ`o322#+"&=#+"&=#"&=46;#"&=46;546;23546!2>574.#!2>574.#q– Oh ..40:*"6-@# – d – ฏ  KK  ฏ – dื)  )๙k)  )ฐ m!mJ.M-(2N-;]<* K  KK  K – X – K  KK ิศ "pศ "ฐฎ),!2#!"&'.546"!7.#ิ Vz$RฤR‚(z Œ}VG+œ0œ )IU!รฎzV`3ทBBWwvXZล3 Vz™&--% ๓๓,(1#ยศ„32#!"&546+"&=–gฌT)>)TฌH6–6ฌg )TT)๔gฌแ66แศ„33#!"&546+"&=`ณ–T)>)TฌH6–6–ณB)TT)๔gฌแ66แ %'5754&>?' %5%‚†Nd––d/“‚\ขข^^ๅช<เ–ว”•ศ–๚  (Abฆฆ“ฅฅd๔ฐ 2"&4$2"&4$2"&4ผ|XX|X่|XX|X่|XX|X X|XX|XX|XX|XX|XX|ผL2"&42"&42"&4่|XX|XX|XX|XX|XX|XLX|XX|ศX|XX|ศX|XX|ddLL/!2#!"&=46!2#!"&=46!2#!"&=46}ถ  J  ถ  J  ถ  J L –  – p –  – p –  – ฐฐ/3!2#!"&546!"3!2654&!2#!"&546!5^๔ขผปฃ ฅนนหจ);;)X);;ฑ๔ Gฐปฃ ฅนนฅ๔ฅนศ;)จ);;)X);dิ,dศศddฐL;!2+32+32+32#!"&46;5#"&46;5#"&46;5#"&46–่222222222222L*ศ*ศ*ศ**ศ*ศ*ศ*,่ฃ *.62"&%#462"&%#46"&=32ŠW??WW??๙|ฐ|ฐผฐ|ฐภ|ฐ|ฐผฐ|ฐฐ*(ฃCฒฒBBฒฒภฐ|ฐ||ฐ|ฐิฐ|ฐ||ฐ|ฐำŽตศ”B76+2+"47&"+".543#"&'&676/!'.6้E*  '?)’ธ T ธŽ0I' *L #3ถ{ถ,# nู  6F82 เ*5#"#!#4.+3#525#"#5!ฐ2 &ศ2p"ศ& 2่D d ศ2d ๔„ ฎdd R , W 22ฉ –Lฎ 05"'./#!5"&?!##!"&=463!2่฿Eิ  1;E%= !'์†yฑ่,2 " ๋# 22+.ฐฆ"A2‡VชใddฐฐGJ!2#!"&546#"3!26=4&#"'&?!#"3!26=4&'"'&'#&#2L๛ดFF ี&  7 ? 9   9 เลgฐ๛ดLR   2 2 ฃ™ 2 2 $์ ฐฐ#'!5!!2#!"&546)2#!"&546!ฐ๛Pฐ๛‚pmpG,Ldศ|„pdิ,ฐฐ#'!2#!"&546!2#!"&546!!5!2pmpG,ศ๛Pฐฐ|„ pdิ,จddฐ่'+!235463!23##!"&=##!"&546!2dddpdpŸ,่ข––d––ข ิิ,ฐฐ'3#3!2#!"&546!!2#!"&546ddd–pG,ข„|ฐdpdิ, pdLฐ'+32+!2#!"&5463!5#"&546;53!X––ย|^––dศ,Lpdpddศิ,ฐฐ'!#3!2#!"&546!!2#!"&546ฐddvpG,ฎ„|ฐdpdิ, p,0o€ #"&54632a๎5่*A2„~ 6'&4O๎**{๎))๎*2A~„ !2"'&6d่)*„*๎*2,~o #!"&762{๎))๎*a**๎ฐ( 5-5!5!ผLcจเ ๅถฝมม๛ุศdฐฐ 1#3!35#5!34>;!5".5323!ฐศศิศศ,๛P2 &d2"d& 2เ„dd,dd  ฺdd & ,L่%1#4.+!52>5#"#!#3!35#5! 2 &d2p"d& 2 ,ศศิศศ,ผ ฺdd & ,เdd,ddศfrJ32 +"'&476ฝ  0ะ   )ื J 00  ืื >f่J32+"&7 &6S  ื)   ะ0 J ))  ะะ fศJr"'&=46 4 ))  ะะw   )ื   0ะf>J่ ' &=4762jื  00  ืแ)   ะ0   ืู๙=ฐ:#463267>"&#""'./.>'&6่ฐ|ฐVd&O "(P3G*+*3M, :I G79_7&%*>7F1“ ฐ|ฐ|ฐศย5KmCKG\JBktl$#?hI7 ภศ„ฐ!2+&5#"&546!5๚X––ซ,ฐp ฒ ŠdddศLฐ!2%!#4675๚๎'=DXDd dฐQ,[uถ}4]ddMoร__<๕ฐะvs—ะvs—Qœ…Qิฃธ(ฐฐdฐฐฐpŠŠฑEุุขHEฐdฐ{ฐศฐศ๔ฐ๒ฐฐฐ๐ฐฐฐ ฐdฐำฐำฐฐฐฐฐฐ&ฐnฐฐฐฐdฐฐdฐ ฐdฐฐœฐdฐฐฐฐฐฐฐdฐฐdฐฐฐฐฐฐฐฐฐdฐศฐฐฐ5ฐdฐศฐตฐ!ฐฐฐฐฐฐœฐฐฐฐฐฐuฐฐฐฐ ฐศฐฐฐศฐศฐศฐฐฐ,ฐdฐˆฐ;ฐฐฐฐฐฐฐฐฐฐฐฐฐทฐทฐฐฐIฐฐฐฐ]ฐฐฐŸฐdฐฐฐฐdฐฐฐQฐฐฐฐEฐฐฐœฐJฐฐฐฐฐœฐaฐฐฐฐฐฐฐฤฐdุd9ศ'dูdddœœœœœœœœ ๒๒dy'dddขศdœœdศศddd,ตd,A22ศ>ffูศศ****ฒ่่NNNNNNNNNNNNNNค"~†ฌไFnŒฤ2bข\บr๔ bสbพ 6 „ ถ ( L ” โ 0 Š ส  X * ^ ฐhด(ฆๆTช*vถ 8|ภtะ*ิ<จฬ6`ฐRฆ.j–ฐเ(h”ฤฺ๎6hธ๖^ด2”โDl”ผๆ.vภbา F พ!2!v!ธ"@"–"ธ##"#8#z#ย#เ$$0$^$–$โ%4%`%ผ&&~&ๆ'P'ผ'๘(4(p(ฌ) )ฬ*&*J*„+ +z,,h,บ,์--ˆ-๔.(.f.ข.ุ//F/~/ฒ/๘0>0„0า11`1ฎ1่2$2^2š23"3>3h3ถ44`4จ4า5,5ž5่6>6|677N7’7ิ88B8†8ศ9 9J9ˆ9ฬ::l:š:; ;<:>Œ>ิ?(?n?ช?๚@H@€@ฦAA~BBจB๎CCBCvC CสDD`DฎD๖EZEถFFtFดF๖G6GvGถG๖HH2HNHjH†HฬII8I^I„IชJJ.JRง@.ฦ j (| ค Lฒ 8 x6 6ฎ ไ ๚ $ $4 $X ศ| ษ0’ ูยwww.glyphicons.comCopyright ฉ 2014 by Jan Kovarik. All rights reserved.GLYPHICONS HalflingsRegular1.009;UKWN;GLYPHICONSHalflings-RegularGLYPHICONS Halflings RegularVersion 1.009;PS 001.009;hotconv 1.0.70;makeotf.lib2.5.58329GLYPHICONSHalflings-RegularJan KovarikJan Kovarikwww.glyphicons.comwww.glyphicons.comwww.glyphicons.comWebfont 1.0Wed Oct 29 06:36:07 2014Font Squirrelต2 –     ๏ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛     glyph1glyph2uni00A0uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni202Funi205FEurouni20BDuni231Buni25FCuni2601uni26FAuni2709uni270FuniE001uniE002uniE003uniE005uniE006uniE007uniE008uniE009uniE010uniE011uniE012uniE013uniE014uniE015uniE016uniE017uniE018uniE019uniE020uniE021uniE022uniE023uniE024uniE025uniE026uniE027uniE028uniE029uniE030uniE031uniE032uniE033uniE034uniE035uniE036uniE037uniE038uniE039uniE040uniE041uniE042uniE043uniE044uniE045uniE046uniE047uniE048uniE049uniE050uniE051uniE052uniE053uniE054uniE055uniE056uniE057uniE058uniE059uniE060uniE062uniE063uniE064uniE065uniE066uniE067uniE068uniE069uniE070uniE071uniE072uniE073uniE074uniE075uniE076uniE077uniE078uniE079uniE080uniE081uniE082uniE083uniE084uniE085uniE086uniE087uniE088uniE089uniE090uniE091uniE092uniE093uniE094uniE095uniE096uniE097uniE101uniE102uniE103uniE104uniE105uniE106uniE107uniE108uniE109uniE110uniE111uniE112uniE113uniE114uniE115uniE116uniE117uniE118uniE119uniE120uniE121uniE122uniE123uniE124uniE125uniE126uniE127uniE128uniE129uniE130uniE131uniE132uniE133uniE134uniE135uniE136uniE137uniE138uniE139uniE140uniE141uniE142uniE143uniE144uniE145uniE146uniE148uniE149uniE150uniE151uniE152uniE153uniE154uniE155uniE156uniE157uniE158uniE159uniE160uniE161uniE162uniE163uniE164uniE165uniE166uniE167uniE168uniE169uniE170uniE171uniE172uniE173uniE174uniE175uniE176uniE177uniE178uniE179uniE180uniE181uniE182uniE183uniE184uniE185uniE186uniE187uniE188uniE189uniE190uniE191uniE192uniE193uniE194uniE195uniE197uniE198uniE199uniE200uniE201uniE202uniE203uniE204uniE205uniE206uniE209uniE210uniE211uniE212uniE213uniE214uniE215uniE216uniE218uniE219uniE221uniE223uniE224uniE225uniE226uniE227uniE230uniE231uniE232uniE233uniE234uniE235uniE236uniE237uniE238uniE239uniE240uniE241uniE242uniE243uniE244uniE245uniE246uniE247uniE248uniE249uniE250uniE251uniE252uniE253uniE254uniE255uniE256uniE257uniE258uniE259uniE260uniF8FFu1F511u1F6AATPรZoneMinder-1.32.2/web/fonts/MaterialIcons-Regular.woff0000644000000000000000000016042413365153155021307 0ustar rootrootwOFFแ๔tGDEFD#$SGPOSh-6เ๏œGSUB˜'?iPไาฉQOS/2(ุ@` s"ccmap)๑๐1เgasp,4glyf,<ฉฮrˆY…headึ 56ฺ้NhheaึD$hmtxึ\9 jๆiFlocaุ˜ƒœjฤmaxpเ 'แnameเ<รz5ไpostแ †2xc`d``เb fBfu ˜๓ ›(”Wxc`d``เbc0a`ฬI,ษcเ``a‚ม2Œล™UฉPฑ pฝxŒ•%=ว;ฎๆa๙ดŸmถmถmถmณ๔๑lีl๖UVwWฉLบ๛฿F”ˆิJปฌ*๊ิฃฮ=]Bq‘ศย… ‚์คŽBO—มŠD8O2ฒก์‰ฦต>™ฟณ๘โJƒV9}ต]ืx{ญำ7บ}ณ5ทฌ๒ๅ?ๅ๑=ำ‡|๔ฺG?{'|โ‡‚=D๊$% R”ผ”%bถ@ๅdGน[~WกฺR]จ^Wรt‹VŸญื?๊IN›ณนsดsฝ๓ฒ๓ซ3ฮญwWuwuOuou_vtGxฺk๗6๖๖๗ฮ๖n๗^๔พ๖๚y3คฟฒฟญจฎซฌฑง?ยŸคƒƒอƒฝƒใƒ‹ƒƒงƒ๗ƒŸƒ~ม„`A…ํแ๊แๆแ๎แกแ‰แนแๅแ๕๚Y)[Q2|s’&็_2†B")พห0SRAšpจ๐ด~A๒}ฺf š„๚Y๘"<>$)ฌ๚y๔—ึ2วZ„"บ~ฉ/;!i!ถsฌKn9ไE‰๔‹๐KอถzFฺ๚ดj”Œก"ฐ&8บกOŽ๕ษ@–IลT!ฆH*)h๐E_‡NJฌ†/IH‚Gคฤร^ะ_šc‚ัHKหTฌG~ศ’hฑวxžAF%F kฝด๑_&Rš\;ฌ๔ti์ฉiบbk๗Cฏ่vdy+ึNuญŽั1ิฃ$eดJTE าEุ•๕4๐%dฆŸFg ;D฿’}fžGซQO•Do‘Œ}ZRลK๐‘A BๆPy}๘โvฝx฿ไœO:q8*7]ก_ บง‡/ๆœ่‘Pํ”]!‘•ษ‰‰TRz kฯฺอ‹แ|w!cxกdฮุ)H{ฉ‚บ‘ตc้+)้Sm;๚Oอีl†ฦนS“ษ)มdoฑ\^ฑ1h:[e๑yฉิ[>ษ ำฃชกq‡šม่ผฐ๘;ฤฮฑ]*ศ™z"HI pIsโฬปฏ'€ไtฺQำ1}Hี[4i)้5o=B๊บ๏+๔r๒/rOB]”:#ฒ{ช๊5rSใŸ,ต=๗VŸ(ตVฏฑU›๓4าฎวƒผ‰ดวmึ“:]ใWŠด฿GฒB7๖ซv–Z+7ฃ+'K9้UM=™้L็อ”UuVLไุๆ?#ณ;9ฦถeื3‘U„ %*sYu3ฒn7Y“ะŽ‹l5$หT๛NT็a ะษT฿ฑทแ‹BEๆี{ž˜;จวร็ฬha=B๋X๙>‰6๛ŽผฌnญVx๒าฃลซfดjL'@ฒ๚q๑P@็uqโฏVะ ไLyTพ•q*ญ6T‡ช+ีำ๊k5Dk]ั[๋ฃ๕ี๚IฉGOs"gEgk็P็|็N็e็[g€3หMปซบปGบป๗บฏป฿ป^ฝW๑6๔v๗Ž๖ฮ๗n๔๖^๕>๗~๗๚yฃผiพศ*็„3่Ž,Ÿ$ะMีwผ‘œ3ŒกP่ดฐŒ2š่วฮ™;–ฆŠแฝ้™๗๔:๖|žญฦโถ›\ฒฑv› ƒ๋้ รpL8Wธ`ธzธmx0๒ิ‡รืรฏฃr4&š-š;Z6Z?ฺQ2ีSฃหฃ;ฃงฃทฃฏใ NใLrีฟฦ๓ฦKฦซฦฦฦปวฦGวงฦ็๓ูิง^Tl๑ต…ˆ 4„,ณ%T๘8ไฉ/R๖Zึœฃ%฿ำ ]ฑqฟ้ฃไ61F๎Ž๏อ.NX ์`”โ›Z.]ไhด9‚๊DJq๛|=๘ช[‘ฎx6ฬž้ŠEž9=ƒš_ ศ™๏8ก๔ƒฐํล(‰Njว•สีvyชiฯXp๗่์M7ŸJตŽšMภุ~ัช:ฒVv_ั [ใฅ๊IbN‹ฦ~สž€C๓PDk๓‹ํี9ี8•)Z›D3้X=œ‹T{Uฐ -ฬ/ฃ 'ใๅไซYร  {ๆล ยสล_a/jB™dยX`>`lํ<฿ฝฐอ*…U๑ลTษs ฆโ“L5O๙,กvฐtณ)ึŸาxต ว‹าต๙ธ”4๏ิีฅv๙า๎ช ตช”cHวฉŽš/ภ2/‘/ีTฑ Pึถ;ฉวฬ+#ฉb_ZŒS ๔ŽLโ7฿zšผ˜๔จhnฆ6ภ'JOฑ8 •q5u{ซฎbIผ`!อหJ6กno5~XBz wŒบ๒ฤZฐาฆi ]W๊TR>M่ร๘>rt%.>ษง+š=\่ร๙k„๎ฏ[| /*t]ต›ฉ๒eŽž่ธ6– EWน›ะ‡๑r๖V*ทBนSฺCจสจ‡Lœ%STิค1;ฐPฎ–v‚P๕CฺXฺี‘‹>๘€ฝ(Qฬ•Gmพˆ’|}u่”๒9- Dv›(๑ใ‚=Ahฐ“,BIกฅฦใ]()ดQบลmึœ๑ 4s์Ÿ๒็z”๚ฮw(1ะน%–ปะก แๅ)Voฆ๘ˆvฆ8ืพ‰ฯคจอใํญ฿šO›(ฬฝTำ‚/ แบŽย""๐๑่ำ1?+า่ล^€J๙๎\…6NรดQ'2K[wฺl:ผ4ŸGฎkOiมถญฅ@ีN(ป ญ~ฑ,๊ษfฆUeEาใ๔-ๆ๒๊ผ'Ÿอw๓&4“ฬfMไฏ7›อ๗v”ลฮkWตJzฎฝู>i฿(L ,ฌl์)Y์้มฅมอมƒมำ‚e5WQ o=ˆ›ฌY ึ{๒*เ^#rฉํš้แ‡<<กํQย5mx.๕–Mไซ“ฬTฬแqi]พGyฎž‘uอซˆ6^52=ต^ังฺYkิงtเจรR$& ช”ร")9m3ร๐Zร๎7;Ž{X ๘vTdŠX ฏˆLx๓!Y4Wd‘™ๅฉาaฯวษ๚&฿ซพ๎ช๘s˜สL๊0ปู›*Oศ^ษจเSJzB:ื๏-iแฆ‰l๓?Em#3`ณ”Z๖lใดV< rกทํzช6RzพซภQgนr`+๐ฤoDษK€da'P๙ท๛ภ{ ตXƒŒ€๕๎ ^อึกฅฬlฯคrŽL‡r๔๗พR+ภfNอ>๐๓ภุ%้7{R‚ญจ- V^ุŠ-ซN๓HrE๛'โปุ๙(R}[ภ&—SH)V/พL!-๎ ]ZสlN='›P ฑoeดฤOฺ˜,,ๆ#ฒ@'c่๏ด1Mืา๓๔=๐ŸyYš็‹๙~~ฟ7ฉ™fi–6Š฿;ฺœ/•ป‡อหๆ}๓ต5ถ"ตฏ •บีuื้๋…นŒ๊.?DŠถ๊ย๘กLฎห`ปฉbฑญC\;‘œ€ฦ็ุEะ”๎ทV๘:๊๑%W่ |ํˆ่X๑๛qส ีฏsoภืห_ฟw2ฌsดYQ๏ํุa๊๗S๓๗Š!?ว3›SdโŽ`,2z :ไๅ™B‹Zf RS{ฅะE'pf4 น”  ณ•‹ิ แ${DA-^ผฬj "ท,ด7๛ปผ๊ฃR‰ธ -b%ุ[ฺDEž™ฺฤU{{6 ขYนฌะ ฮQ ึB๛S‰๛ฅG๐ถ‹Jน&Sคญ{สWR {ฟ<]M๓$^’w็ ๙iCr7›ำอๆS‰ีW๕‡ํ็๑,/๋ใƒk%สYJKณHตzCฉUŸYบU๊ิ฿†pฉUฌ๎^)•Šw#ŽๆˆŒึ”*ล‘ั…Rฃx1๚2.K}bฎxมxuฉL(U‰+ใ{ใใO“4’Iษ“๙“““]“ร“3“ซ“{“g“ท“/หฆ\)”'”gโ๗iœทพ ๛ฑy]nMžแWdœC,>หง1ะ}ทซb|ธlHhน“จแrใ”ณ๖๔%XqzSธเญ๛ม:๊mฝ<น“Lทบ™ํœj ์€พ หฤฉ๎หToฏี9|7ธI๕Ž๊๚=๚f†/ทฯซlพฅ‹Tฌกโdหจ<ฒ์ฅ;oQgVพ ปด!ฅŽดLkQc=Yฃ)ํธ†ขNฐ ฅ8Eเ๕๒๚ฺT์9?(twc _๚โŒ ‹vิ]m”ล†:๊คY7ฯไ๑ŒSห+^๋k<ิy.=‘ฆศฉŽ,วึเฏวkn6ึฏลฃว็p_/ฬVน}gzP5วj…Ÿ๏ฆJพ!P>ม•ฏฟWo22B^aoื ษฐ๏:ด-Usำ_้ศx~ศW{dคV๒๏ะlซAKๆ•9‘U™จข5?Œอ_ดr๘.กธ’ป‹Hห‹k_๚บ=šื•~ำวะ็๘hŠHมKฃฯีฆWตฏ๑}ž.’้kŽšูQjฝ-๕€N•ˆใอ™XeฦSฉํCดC์ณฝิํIิ7pใ)€๎•WรšjXแ)˜–bฤฑถ1ฐ}shbžิึ‹ว( แYฅง็็ruตi0จ๘๖ฎ7ž:๐5‡Z๑4กทํ™V3–=?อ‚ํ‘Bwm[+›๒ไ๔ู่่„ฎ+ภช5Gใ%Tฯดฝhถค๒07OฐŒปzTO,฿/Twญฝา3ฤU๓โ=•V%_B@ึ์ x๏)vฅฤ‘€[ห‚เiŒoKกบ…•ญ๋าดฺพ:%1ญใ๒ M๒พ]่๎zส|›GI๘V๓-%ฒฉพCีrKพษใ ๙FRโ;9š๊ฆ=ข%ฌ ๖|„]hjซ:NG1=k าŠZฎป%6๘D”i’S.š์Tjซa^Q]ดฯี˜iบ3Nwคํัง\่'ฏœr‹โkี‹PW]U%_ƒ‚-ท(Qš…B’B๑_:Y*ฟรoš=Ppฮ๐8ญแด/๑œZษ)Uฏ&ญ฿9…uิi)งL๐iดDฒLvb€Ž’˜ก๋x~ฤม๒ํ ๑‚ฺท/๖ˆ˜ท“ํ4ึ๗o๘ํˆ/œ ฿๊ฅฝฺผŒผ๎—๎ OD^ใ TZXt๛–beZๆ-ไ฿๓อ›ฮy๓†๓Žn@ฮ~w<~i^s๊ณด(ฒ1๏๓ฏไ?๑t Ž$ฺ๓.2jฑึอ๋ศDฃ๐ S๓*<OนuEฃ๓ฏแฉ?j(Š‰X๛ใBƒ— ™ณผูึm.7w™Q๓1hMกตi{:”ฮคk้.zŽ^งฏนภCผOใUyCšwๅ๙H>‘ฯไ y=w}ฒ EEฦ๘3ี7฿SLาsk9™I8 5๕ัึ#ฯYJMCU=๊cH?|มฏห‰ทู-ึ~บ;FZบš•Qp–Œ21%?ยŒ1๛ ™ฃฟฒ•O๚ีโ1ฆฦQดLม๓๒ˆ†๔๎*โ:””ชํบม$”lN›cŸPSjt.ษ8 ญขฮร•‘ปกQ"้ งิงWOfa˜eQˆ0d2ใ–๐๋(ฤcุธ็(คy…ฏ“qE?‘‘€„oฟฆึ•™ ฿ฎRัuf6(งฬสิ3แทs=จุ๐ศjจa๒ัt„ษศII4ฮ๊ษiชŒฺKฐุ๊r>ๅ+S‰จLภำ/ฒ_t <ีg๗ื;ึฃŒa:Œ4ฑ,Xพ>€e0'โVผ„/MYสฌkv7'šซอๆ_ๆ[jะ"ด2อฅ้Dบ˜nค?าc๔=††Zญแ;๛ู„HYiํิาษc}๕-•j๔jZ %รoV;ถ4kŒจGด่(FT;t*สrีฃ๑ญjU3ๅฤฉ@zซV*+พCพ1O=„`ฌ™ัํoฦ^๓ ‚ิ|5†•Œ9g ืd>า[ะโ+„ฦรขฏฬ@I๑โa””5ฯ๏ ”ฺf2ข3๏ๅ 2อฬฌ0_ใะ0W๏๗™ั0Kฏโึฬ๎š=!ด‘๏ห#๛uฎ‘[,D?lฺฦๆ๎ฬ=“{;O๙afฦ–๙}๓'*๖แo๙๗๓?๚U‚ฟ”ฟผฟพฟญฟฏดบฉ}ˆxภฦ[ˆ€๘ิพฅ>๔ู๛ำฦ’TSŒด=ัโ]ฅ~๔v๎แbัh`พํ ฆํ…๒้œท(ชฺq< ฝ๊›t|บง=`ฝ}—กGZุ๓c#xnฉhoผ บํ่ฎื/PT๐ฝฒฺ9hXู1{+hn{k>ํfฃkฬ5บ.1‡ฏค](Mฃจฆ=ฑ๕ไ๛ๆ‹tปyeC๒LิๅiคW่?^ฃ‘M%[u]E‡นัฤ๊pด›ิ:kุรŽฅ๑Oซอ$$&OG}{วหข–เล%บซ -ตMSVญ']‡๙ต6jฅณ6ฯUรx๋‰ฑrี๚๎?O#๕MtซฏŽๅQํLKž„jg>็ePํฬซแLชcฮDๆมKฃขc8^†(F‚EลžN๕#ะ6Mิ้๔ธพ=.๛|uบฟ› ฆ›PŽ๗E(œุธ ๅt๏(fBOก฿„)_ยณฮศmต< eป๎ฆRษ๎$OMิว๋ซ+ำณJ่ื(JB4ษOจจึ๏Qn—;ชmีะล7j฿†ะBzห--ท๋แˆณ…y2;ใ‹“V|ฎส็–!ๅ4๛ศy›ฬ7๛#HฏS๕คฮฏฺ๙ๆ;ํ'๋vm:รŽŸๆ๑Š7?%[็๊Ÿgล5n ืธ๚ูืฉ้‰๗ั|‹’Ÿห๕+ข4oฉDใใ>ฎMdพA1QSŽrอqชKiฎณฉ,‡‚~#eเ)(่ลน์ezFK•‡u.kข–e๔„ZJย {๘ําุ์ฟ}_i]ไๅ/๛.ˆ๙y7cŒžำฬุุ‹๒๒ฎ|แ;ด$ะผŸ—”’D‚‰พตrฒ~ต7อไ$&*4Gท๙T๊๛U~ืy†๘C๚ตว8zYพJO"Kzู˜6< ๋GŽ8ส|Lดgt32ั>ัzศD˜œ#md๔82บ†&=Ofำฯยำ5OfDN<ญโX\‰๛๐œ'่๚แสTQจc๎ฯGWงถึ?ฒ'*Q>Y|๊b[‚็h}3๖Ah›ฏๆ฿๒#<สo๒็yี0†ดPEZู[฿›๋ํLjˆฯEgr€ฎ@=^จ[ ๎tวิฮš•๙I๗;ฒปJ`ฝWŠaj๊k5#[4โq;พx๔/4ฦœฏ;ืฝPsฦ์RV"7oRt๏P{ไฯ๚wฦ…ชŽ๕Eต›7@%]ฬƒ๚…J‡๙fลfˆec˜ใ๔ฅ†QvึกHี&VZจG~H๕ค<Œ`ฬZU๊gi-‰‰๓c(ูยœ่N,ู8Jg}รdPŠ๗ม^`qvBa‘h!mh}ญญด" ่Œค1hš‹BšฮX]J‡ี{งrมW พ/gหี๒@yคผHyRy–nU}ุzg๔•yeษซo-้หTฌSฮฦUŠAซ๊k-œ–07ีvnข =2๖่KY„MดิWY]-๛Žมฏ—๎a5 ‹‡ั๘ธ yw] }V“^.งงโไkˆ_v,< †๙๗HไญฏŽิ**๑ห 2vโ ƒž๙์๔`์+ใuะำ‘Fๅ(]ฦปD#ุ-ฐqƒิkธศถ– vY›า4ฺdy[ 8มmฑ6Nฃ่I๏ิXฦ ปใ*› j้ถŠจ์O๙Œ/F๗X+Lฝj ๖ลY็ฅ:nšฮ้qฟAท“ญ š' ๙๒†เ%i#47เ€อDOsํwhฬ๛๛ฉืRฟฃejMผ‘9งฝ%ฎ…ัีyEŠX ]้S”~O‰ืGWGN้ผ`]iCš๓ึ@=5^สษŠถ6l๕ฆZิฮ‹ŽฦCmพ'ไิl4อbฑนผ6jฉ9IพV,ใ้ฯจฅื'utŸ๚yริ8VงŒ{ิ’žึDฮFeฐ7jย #jc*ฟ8~๖QKB-๑ห%ฌkกššH๛โจฆ๘สแง๐;ีฑพฃ‘๔iจŒ•ๅ&T•˜๎้Sภa†ปซ๛๋5!ใ™,*๓eๅไMPqWiQBW:Wฮะ€ต(wืGYๅ”๊ใ2g›7F95๖pdษ๒Fฉฺ$JdำtmlmPFVขAืCย[!‹Gฬaฉบ†Eะo‰ภZซ ^ŠŸP^๊‹hl_nาลฦฺcวห2รฺdผa™“๊๓{]ฦฉค๓๋ัไ5ค(ฃrŠ.”น ขšFใแยิฺ-ต)‹RLO'>~JvDวVขWPNLใฎGiฒ"์Sิ>NฎฯEร*kญUJ็ฃ€ตฤฏ 2p;์ฉณ‘~กzโ]—CคT่ฏOขเฦ’ไป?ึ"*ี›rYโkUmูคญQS+าฺุaฌฅe{„๋งปP/8ฎตtœ“งป|ูw๑‹ส ›๔2|Kณn™A|๔๋5!ฃ๓๖๐Sาh-๘–วิ๗bฆยpลV๏พpฌ์€fๅ„ผPhBไห5#๏สyๅ๏ด2๒บƒ5น5๋ผ*๒.ฟ๒6๊Mำ,nฌ‰œ๘ $ย‰:#™๖+ๆPไ’œkDN๊ขทsศ%๗Œ^DVๆ ž/ฌƒ,ๅ๏‰จ๓*ศฦ˜Xฺู๘]Od”าMz หตž”ทd5[#c5™งแ้›†้ษ›มSŠ<ฅ˜ˆ]Oฯะ๐4f]้ถฃ๓ H ~,๛ฒ06ว้ธฯแCS2ฟ2ห›นๆPsพน]_rจR‹ฆะšดต`%.ฅ้!ฅท้[.)žiuœwๆ๙h>•/ไ+๙f=?ภO๐ ด=๚mฦ‘ ๔โธฑ๚VหํH_Oฯฒ}5us๏Ž2บ5๏บฉ0Fธฒ•Bใ=๘ผณึŠ๗,•ณพทfiจฟ1๑บ[“.ี(-ฆHz u๙@ํฏ’G๗๏‹ ต™ซฌ5]งo~ำ%(้้าธฝี๖Gัํฅ4xE‹>HD}อJNyฌน†&๏Š‚ฅcLฟ#QHาO฿_นพH]‘ื๚D_)b5U๒mYฟๆิ)'ง]ึฆr I๘–3tฎ|v*๒>๐sึx7ไํผeๅTึทRโง]คพ%'ฌ€|bถ˜oJl„,œP*ŠE]noสป r2ลฎSนฤ˜YฺXฺ‹ฒฬฬแศฦด4ซ ษY*!๕คmใผ4S|๏โ=0GษyoกIม“T…'œ[็ฝฌื}0†Pๅภ’฿p4ฟT„ไ ฌ‹นุ™AQฑษW;่`งพเ =/D๓mศ&๔ษgแAP฿&FS^=YโJ<๗Mู,bV7;šc-*2OMšDkำŽt$O7ำC๔7๚ธอ~._หไง๘_fบ]หฉรหำ;ทu^๊‰zงถj}l€jค‘ศทR๏ๅQMZิ1b=1]n*vŒ†๊‹ิทใeว^ˆทz๒ข๑•kV฿ูภF4SัD>ิ๖ำ๛ฬข๋๚๙ ˆ๖X?V uใจŽRz†ีใผJ๖kข๗ฺ9ƒข๖n}]‡%สซึŸ<ˆy+–ฒํ…/ํM~pข\nZล™lŒข5™฿ฟ‰S.ฑ‘วกฆ4"ฅ:บžู–Rป๓Šศน…8๒ ๗ยxชกโmK%’กqัMQHฮMฅๅe๐u๗โ๛๏X๘ฺ?ฦ—ู8ฌHdS ~•ƒศ+wGัรรฅDFS?ๅ†ศป{LW aวฯ๑?ศiI๊hIŽ์๚ุgใv<ƒw ™ณ€™aึ5›อ‰ๆBsฝ๙ฃyยŒš—อปๆS>Aพฅ๏ฏฬ๛ฝU๊–W$๕‹\kFงร3‘~1ิPถ๖ๆ๘ุ+ ่ˆpG!ฃK‰;๒#ˆGv_าไ%ู*๖NูGb•ไฉฦW๐ShลYz‡ '^๑แ™*…ฉ_Fซฺ [๔r•mึฯx๊A^Z5ๅD _#็`ปC%๑ŽภKศA๘HOะศ ฝ4“ฤƒLด>>žf{฿ Ogธ9แ:้ช0๐๘$4ิ› ;่f~ี๏๘ฑผฝm•ˆXIOี,†๙ ไ>นจDfH๐E5่ร ha"zผqํ:xc`ab`œภภสภภ่ร˜ฦภภเฅฟ2H2ด0001ฐ23ภฃ`a@ 3™LP5Hฒ ฺ„Txฤอ,W‡๑3Xปํgอ๎3vwbถm…ุถ“BPŠŸm›ทปทc‚ฮลwช~๕3WDJB•"IK”คฟแท”dุŸ~ŸN|""—ห๖|f‚tหqrฅJฉีG๕W5RuซƒิjขšขfชผŽ€ธ?รŸ๋/๕7๘Apr03˜, หร๖pBจยrMนฎ\6?.a~Sม+์R˜UุTุR๐‹‘N่Zข{๔๚"†^ITีEร฿;ŽEd/Ÿ „—MีO V]jด๊U‡จืิd5Cอ๖w๑๏ๆๅ็xy‘ฟึWมภ`b0›—หยสะŸƒ๐ำ\KnDพ3฿ŸQจ-๔+ฬๅๅโGบJ7่1z}พDฟฅ—FษจMคP พ‘ํ‡]ุ่™˜Eฟลnะ๏ฐ[๔ปฌ/ 'Š#=‰HD'่ษlj้ๅlZ่ี์๔ะุ}pฐแ"บศ^‚7DRๅ์[XL๏ษ.‰„>Mข”พ—-G€a๔S"๏๗bo๚ฉฟะ Eฅแุคˆเ—ฟ\*]RZV^QYU-็jคถฎพกฑฉนฅตญฝร0-๙~๒ ŠJส*ชj๊ ƒ\…ถx์zxXถ๐ฝŠ-ล1ล )ถ“b+‘’บฑ-+CฉJ้tRœyp/้n๋ๅฮ,uFหฬ๋.3วหŒ฿๏.ƒ๓˜น^ft฿นWr,;้c~ฯ‰ฎคซซsฮ=๗๐bP!๔jlข1ฤ!T‰T"r%"Ežs™3ุ์ต"ธ‚0r–ะฯ?๋~ ฉHลก2Fจ IผSโlพXลผข(?pแย.t-ซฎชุธ@๎ง&}/†D„jๅZต˜gใQ็]ฝยK<Ÿzมฦฦ 6nข@ฺ ฬ าƒSRฬRUภฮ#w:&tpฑ%aซํิ๋ฆชธูkภ(๑ธ ใ(ƒ t<Œ&่*š(๒9Mืช5r”žžซ’ฐi(†ก<ฐุณฅ๘t|ษ๘์อ‰|โ’j๐zQŸR9ํ‡โ๑Pุบา‰$“n.ม\ษL๛3dืœ๛Gh vjy๙ิ๒8/ๆค๏์2้๛ฅอ:ไ๊  พMแ)จ ผซฺ0j\หคVี5‘“Y>.”kZต(๓Rjขม`๔ด}.๊ั~ำฃฟ4๘-Oนๅ†šBัฺ๔คi‚็>ต~~i๚q๓ฺส-ทฌ,฿<>Gไํๅง ต`v%ด! ธ$)ฯ๒€ธRิ„ปZŽ๛uเs$ฮJ9 9RญUฐ๐พ`š@VUฯำ ฺwฟ \๔ถข‰D๓้ฏ“F๐แป‹ษศeDchษ:˜@e ทจ”๘J (ุ‰ญฤfฝ~ี•:6บญฎ๗6ำ็3ŒM‘9|€œcฝ†e™ฆนiSFQ๊–ช*ชตก มยŸร\yฆศI๓๛X๛cุฒ,Œ,2Fƒ1ญ˜˜‘d‡0โc๐๘2ฒฌ๖ภ•…Pฬž‹ฬ้"/LญภษZฏŸ?oฌ†•5Œ๓˜‡  “tภำ‹ฺ่ฉ0หฟคาคt:"ณq‘5Œม2U`ฎyนhูj!ัๅใข`0ปษ,ลขV! LฺฑIq˜ู5Mัึfy๐–jฑX-bพvz|uutญtu‘bาJก!r๏‚:ๆvย•tขŒ:YRแ Hž๖O„๑d|ฒบŸ1๙Ÿแ‡ฟเฦ๋่O%Pษ ษ˜“ˆฅ.ณA'{eฅHนwH&ไ(โ=‰)iœM‰jZ3–๔แv๏ตฉB^ป_bŠษI๋ๅz(โB.F*2qdfŠฬื-ฑ";ถฬDคทI$"‚b(ŠjจฤEดz‚ืษฯeม‰%˜ˆ.1*g\$วำk4UำTก:Vt๓ะํฤ@ดfฏฃ$"XEศ๋๖ช.Œิฃ้ไธ2ฦŽeu:ŠาRUp€ปใtอp จkี‡้„EP3๊AvLึtเ›f(*dมธf‹-<ฏœ๗๓ wฮQฟร]Gtฆฉ฿.p"วษœ,๋ไOuQไจ#วจสŸ;zt๎8WพฒC‹ฦ5'#‡2™ƒั“ื\{‚\Šœ้{ T9๓\ฌฌSทธฉ<{vc๕ฺลณื.~gv#J๏+ฏ]˜ํ}q๖;ไ[ถ๛e…ุยMJ3‘-I"ึณeBtฑ…•ึ๎‘‘ไŽŒ pSOk~X้{wื›~QkวŽDG"๔=ห5U€TwG๏Dต€&ะหภชŠ\๋า%l’ป>†DLบ.ะ C‹เ›ึืฯ;T ๓1บ3Z5ัจmZCQฺ'ฺ๋03G'-€ๆฤ|:ฬ‚ฦyuฌB//๊f‹h_F]F-~u๐ฮ&b๔oฌ‘เสƒ(‚x”ฐ-oG[•ฒ่%๓บ pเฦ9,U19๊uZฮฐฅถ“ฤnzŠŠbัIlฯกKyš#s:’วแํXAไ*Csำด‰‘ฺ๛9ตม“8๚ภฯขSSัft # ถŠไf9{$…นภL1J#/PMkR\$!™ศ๕แ|อึ๎๊ r๙ฦืx\qfฆ8sŠ๘วหˆ8Ql(ชช6'…dR˜ี๒๘๚ F`hฉ&ะ1NŒAฌKฤ–็๘G^ƒuฯ ๖้2ฏ`hšu๐‘ฆฑeŸะุ๖ป,P>oYั+โN <๘sพ>€ิ6พ5 บ‘A7‰™สYBว ๖}๖‘{ArีตskkZ็ี#๗{D[ƒŽzฃแŽ"Yคณu^าecห˜ใฦต๎[8(ทะ๛AL?*งด็ฏœžƒะƒจ%ฑฐE๕ํ[ร"v>Š-"7–k ƒX ’aŸ๛่eคช๐œ ืภc6๒E 4Cค) โHศญๆ ‘ะูถŽทŽฏ=ๅˆ๙ฟ‰dธ™บ;๕นปSwƒBoWๅฺท’งฝวผแ1yรฑMู์ูTๅ(–‹ไ,Bกhqด‰ฒ‰ด ์บ‡Z•ดึึV]ูชต๋{ไั๘Y ิ๎ฬ…C“ 9’ŠnกZUๆHVt&อ‘่Œฤฒx็ท๓ษd>๙ถr9S,<ฐZ.ฏ–ฝลBบ\ฦณฐš$ฃžW~Œชึ l™ ิษe๚๐์H†แฒฉQ๊ฯt๙สฦYVk‡%๚๚๚ ป! z`XTru^ๆ อ0อเ@‚8k^2ืศiˆผฉN€Wั#ฒ4็ด†ูh˜8[รFฃทูx๒}ฉ๗๕Mkาฑฮศfนบ=ฤำฮฅฉE็e™ŒZM—yYา€Dศ`5มŽฑeh4ฃล'@ดธP˜Oˆw4ฬ l^Sฺณ๕kธชแึํ‹ X\˜™Xเขwผคamผฝr"ด:{h๏|.5ใ๓TKIja๎OSiๅณ ฤ ฺ `>ฅซ๛oพy้škJšึฅ3t่์sฮดgN„nzB๒=‚3— pโูัผC๖๋|ทล๕ุYํ6,&cี-`..ก ่๙ผmKežฆา`ภ๙OlgณFำk—ฤgขie็< gญ^”GK๘ซ๘Kฐฮ้ฮต-${๗Vdˆฆ%Yโ$ฑยล$Ÿzฺวธ๐ม๋nR"าบD\ไ=ๅสF๙]๙ิว๓ฉง=jฯชUฉŒ^&Rษ#wq†ฺ็ภไ"€ยก>ฆ‹ธM(2อคฺไสฐ@( ฑ?J‚rฉื ฒหฮWั5่$ะ‡„C>iศใ๔8œฅ ผัะ์dt=!}๕ฟ{ฦปMX*“ั’ฤๆyศ๐'` T‚Hte‡xVแ…xฅRs^ิฬกyฺ€IpR&q“ ฐŒ\๗y๔˜0ใ๑3มฯแqŒร[^4ภว๘` †ฑQ:ˆ๑ปร™ฺ•ศ7๖ณn=ไmฯx˜a=a.ฝฑqภƒY ไa{'ก“" #f›หeด fAHฐส•ต*p˜0ฦอ‡}žหrQ’wY”ŸqLะ[)0ฬะ1ˆ;๐! '˜g(๔2d2๏โใŽแฑR8ฦ`Œว€%พ๐cœ’š€)1A— sๅ_X ๔]ยฅ-P-ธƒhhGW0ซcีFใง/ ลณใ‘†๎GฯwอฎZิ e~Wฮคยd"๚sbข_ู…0–๙”Žฐฺฅ Ut฿ฅ กฑ+่‚ถ ~ม1แบเ๑bฦ?ช CsU/พ่ซYGB™๎ข฿‚AŽG&dผ;N‰@ฦ #}Pฅ€รฉะหฎช7ื2นGษฤMNฅฆRAŸ uUำิ4ญ–ฌ%“:;ู)ดฦ๚ฺฺz"RVJLMmXkk๐฿%œฃ …ึฆชT5iฦ3B$รโ ZŽ&ุ^;ม>ืkc์]ฤ็zป†AR&E–ๆMx›X‡8šBำ( า*s2ง๓d]๛ัnY€‘ฆŽ"ฬํ๗R…&# พ"Iu“ฺฦ์^ห"L#อgTRัท„ท3@nฐ๓b—;์iLX๛ฉเ ว“g3ฎ=Xขnฃ’eื6ฃ‰~ึ…ๆ‘๔ฤ•™๛œd‘ภ๊[ ‰\mฟrM฿ลุแVJKฅjฯสชฤเฉกhw`Aภพตm›aYkิฦล๋6ฯ็ใZฑลฝ3jทฝ€daDFa์ส‹๖ฐv5 cสˆ์ˆƒ ‰€ 70Rฌฏซj๋๏ค ๏ij6C@˜๚ภี$‘ๆ ตŠ^็6ฮgทแ็฿'€6ใ๊2?ญ้ธ+ฎปี#Tƒh•5\•่*T ตัฺฤ?ž็คอ๓(@+‡็คยœrj**ก บ “คQ#”วYื1zฃฉ(ถ@—๋-•๛ง:ฟVงW๏ดมP)สfG…n#บ†&้ฝ้‘B2ยน4B1Aิkข@๋/dซต่8)ฒฟสฝp๏CJู9ยฉ‹จผภ…ว‡“ู[š๐่}็"y87๔\„ว @i@’็H๐สฑyมชz-+ $vโเ"Fี=l‚ฒหžล๙ณ,ซมM"(c#| ์ Oู=๗k™ {C&H๖ฦvR[,ต;สOา?y!nv^gฒฆ๗ฃใ๘น๘ฃ0*ใtQโaœใ[ kV๚๑ึั๔Qlฎฏ?วJ?๎OฎO_O๙๒B๚'lOa8 โทคMตu๗ธwทิ๛ จA=1ะน ชใึO^คเDJ์ุีtF…N„q2Q+/=ฅว(K[ึาJzท–๓‘ทl,Y๔yFvโv^คUฬ`Ÿฃ๒ ๅฯV #•ึq:&จIซ— Umฺ%=C„A๋MM2C*ŠAEธD๊›X…wฑ Cม ต"ง(่J”xฃ‘5!ชgร„ b฿Q‚ ๚ฟEปภ˜ Vแƒ1{ภกมุัCGœ›žž›~ฒซnZ๏m6ณS้๔T๖๗ฆษร[Žขd0ˆDk.ศขข!vUฮึฒiป|™ทƒRrCท†๕ฃT๚_%_pvNเ •7่ฌ๋ยGGดฅt็Rษ็ ฑ PccyA%™ยDŸ฿ู7ฬOJHG๛ ฺrrPmœsmไพุ๏wžy๚๗#็>Z‹v_ใบ>Cฯะถ๗ักธK๏ฮ8ฃื฿ีพฦี๖์ry/ญ~‚2๓ขพฃJI~ฎ6O๊นถ{]]{ŠAZห#็%/ˆ2ภ‰ุ}\๏ Bฮ`v}ฬ“]ฃ:Vษโ_ฏ ฯี6ฺ*ขMw\PกยษdซPคม6ษพŽา„ร€ฬใbำผx‘jXไ๊หTใg€p LLฃปพ"ุ;M…aด”>๙ฤbTxํ™ไฑorฉซKื)๏แๆฏIaฟ|/yึ(ฝๆk=“WซวRใฉซ‹ๅาำหืคzื—ddWp0ีlลฉะ5&๕ธิ9WB2พ>S๗วaCAu&J"กlา '$44ค)ฤงฅJE‚พ|0•ส๙๓ฉ—ฉ*6‰u‰$ ˜ถ9\o 1$Q_“c9n‡์|่ขž‡ ฑ2๘4ๅg8”บ~rvb‚Wๅฌ็P๊Cใโไแcๅ–ูใ‘ ๖?P๊ศิฟŸ๏ฟ1ใ`ภCฃ‡๖ฒฒ๗๐‰๐”ฑWDลMบ<ฺเlต ŽccพDฟ๒๑=ๅ–ฬ‚žคจฐ?C:[ทฌdNญ^ุV^๋†G์ฌหบ [ƒcไถถุ%“hRk๐vว๕ŽKŽ=( ๎_}ตทฉบฐทˆ[Wฑ2 ฅ‚f๐ฃี†hฟ…สฟ\เeVไ>?=฿Š$–๑฿+_ ฆ็ูdไหc?^9๙ฏ'ฝ*ฮ1ส่ภ1H&0uข#ttz)๔ฏลIi+m๗ฎ๒x#๕&์ (๏S๘)žฮํ‘๙ฅซ:j!Sพ๎ึ ™,ฟฑ$แฒไ๓HBR[รŸ`ƒๆอdz๒ั๘RขนF*f2{j{NŠ…้{ฎพแชEo<“นp๋uๅŒ<ฟบ๑{’ว'•ฑไ[ำHŠArBวF—`ญศ‡ฃ@5วKDศ?ฝา๗…๑q)ซ,„Y6ผ dฯ฿z๊ึืะอ๕K4าฌ“ถร/๐ŠšซยฉšS“Ÿ)fxŒŠdเ™กิงศ๙zCB๓vฌฮ=tx9Yuู4เG6 ญ+ใ†ฌAPUส“ƒ "ฒKใ.VถV€ฝิ บ#ภั๘๐น70้cT~๛X๚˜i‡~ำ3=Šq้œ9๖œs็๎XK๛ฅ A` Hลoว]ำงd็cdๆ๎ZLฑ โ"$ึ์ lX&Bก‰'‘FjN/ฟa9}๕ย`:nฯ˜ญV'4๑%xM˜<\N_ทฐฮPซไ|ใเdHU฿ำฯMœ๏sžšˆ 2 ฺุ"zLถ๑ธ้P บ/Eถ-ทณVCo4h=ๅมD?้‚๖?%ุฉฆต่๗%ขd—`YคŽิ๙ิO ใbห4ภ#C2ฬํ+ช7ฅrYjจเc›เ”œ›]-ทซ†X6š •$GX˜]^ฅฟ๖p|ŒP$ุBœ]ฅu ๋œgห‚อEJ{œซ€qย_Qี•…K+ +*.}่ะR๏าากCwNญงฺ ๔!m_]‡ษcฒ บdK…ป[b๐]Sซ$–Xู*1oฯ ัRโญ š P จฌ):4„บถnšล™Kˆqโ. ๅุต^๔Cc ‰จ"'S๏Œ)+มฃซŽJ๖7Mฌ๓ะึMํ'ป†Š๋ชIพฆZ๚-Šƒƒ(GM๏ฏ์ฐŽ9ซฌ_šฯล’ฑ;&&#๓““…=ณ๏ข๓ูภไd ;้_ƒAะ„ณB6+|l”6๓ธใHlๅw—Zฏ@7๐uzชสXu 1Vใq>=07'ฬฬ˜$ภ“.‰ _ ‰ฉลFAศœœ&E๔0<ใ™yฝ^ฌ2cุใ ฅ/ฯLM‘T๓ภึ๋ M3žwฎg๏• 6ๆa dื#ๅ๕เใ๓1Y'"Dถปฝค{f=Lฤ]["r`<์น“pxท‚pG1Mฅ%ต“มNF f๛๑B๏๋ธ}ดžGj{T4€&ไCEbฏ‰w็Aวส"š—I9ถบl$น๎„๑.คI0€@ฃ €D €์fAp†3œž8ป“˜ูู nึ+ํฌดณyฅV$๙$K…ณคี๚”หy%gม’ฃึฒO:ฮ๗ญƒฌปแๅ๓ึ้๓a๏ฝ๊Š 9’๏$๛>]ะจชฎฎ๐โ๏ {่uขษ>Ish๎ๆ&๛ฉผตWˆ?มwXผ๐ิ›—ž๛สžO<Dม({๊ยาอ/ๅน‡>๑ghd4ๅ’’๘tA[ถ”f 65>˜˜ํ>้๋Xeฒr M:อ–สœรง.ณlZz6็hsูVNo หS+“ซdwฺfทฦหHi)Dึซ”หfW#*ฌม+œN)†๒›l2P zฅลg,ฐ‹ฉo`™เ5้m—O”N0ฝ“?\็ำKฝฌjผzYื฿็๋m=sgฏoฤ็รuษฆ$ปธ>†ดXฐpิ–D คQ0ศตb7ภ็ไํิœต`ฯ›฿ธd˜ฮK|๓iปhXัr.T h๎mK(˜tไ0€~A๓ถ)sอคซd+๏xCo๎!lำ๚๖่ฃ1๛รNจ4wำz1ธj:"ธJ ˆซDSฒF๖>JหlŽ˜๓ร%ผข“6Bเโผd)˜'ป]`รf็†[F‘|ฏ2t  ตlzนs'1Xลจ่‰P่‚๐ๅq&•Yˆ$๔u™ํ ฆะˆี”+๚r่ แ‘pxไ”๚ T์๏ๅY๛7M฿K๒–ฦ็งพ่฿‰KศอCuTVัึต“[–ู๒8บYNh –Lร€Yา๔ั๗ขš.00„ŠžC$ยธw‡ุ/dOgzŸ๕4ฬv๚ฟfซxงฏยiSVDoฌŸfUู`meาOไOฒw๑‚ƒฟ๚ซม7wŸ‹^O=์{่!๎-Fz[Y@ &ะศจุ4™ิ€ฏfฅดกiฝฎ๋ผo$มํฐ< ™@"Ÿ{์Š๗1 nฏkV†ฏ\‰กๅ ศ%[ฑ"ฌฅ5ฌ^๋้ฝาa๊๕rธYPซKบฆ๐sTIy,Fป0้‚uไ8[rๆณ6ฺ3่Š%:k้No4H vyŽPtz—6ๆฬฯy–’ƒฃ{ ŒนๅžฮมE๗@Wjถซฟฏ—๙ปไ‡ำoฆ[๎๘วŸ7‹วFณ#ะศžยp๐R6š<ึ้๗qฅ7่์š้Ž†ุ หน๙-†นพ$G4|`ซึฎ^Bว๊(u\j๕:zส0‰›X+ฟำึœ“m>ง]นุห๐vFศีŠ†ํYy|iiผTŠfณัา๊Zฃป’u‰•๗]ุ7t˜ฉiำฐรำ๏ฃด๑๛˜ƒ•ผJฯCถต)š8….ภž;Yปqziแfl่๒๑"‹Pปat\฿ก๏—ภๅ1y–4ชฦ‰ซะ$‡่ลม๙ๆŒZ^X+•ถํ*ุฦปขอvu}}ำาtP“ืเZ:ฐฦ! ›๚๋๊ฟแใ• 3z฿vหฮW*6]D๗แำฺwสฒ )Sฌ€ฺN๘rƒ๐[qๅณWc%dี5 ‚MP6Yh!?ะฎ E๙% n๕‡๚๚B,ง๐iห…8F1—อ~•๕'IR-6]>ก.u๎ไ0†M:ก_ศg๒0!ฝยX Œƒ๘ผดx…`หท -คไจG;ญญ[–=x@_ยdŒ_*hถใด๔ณ€u;Z๘)5ณบNZAL`™ีๆึ‹ฐ้X3ิช๐•‰฿0ร„{ื™ค๋ ๎้5 nญ’ƒ3แ ดx€[aC๋w๚^‡ฏื +โ็9ๅ๏ฦy3ฮปuf4เฑaา+5i็ูU๊Nฟašอษีษืmขพ;๙Š“ัธK…ๅ~ฦคัำทว๏I3*Iฬrfjcc๊ขFตฉฒว7ณ$'„)ภtX๕฿ผฐ4}cํไ>˜๕อํ…ัS๛ปmK์L9s…\๛์ฤปiโ ฃo˜x์มึ ~๋๘๘ej\5XB\เ^{ไI่]X๓ดย‘yืฮSd9คี-๓$ำKฐ––ํู.‹ณๅ64 *“jแ๚4เN•๐ ฟ๎๋ณ)ภ>_ส็kัh5ฒœI‘ะU~(จx›ษL๘1ู(C†!ฐHฅฐ2H ƒujp3Zฤฟื!‰h›คU~)ห๙gE%‰ใ˜งksAp†฿!7ธ&  ฮSร\†E๚(พsRN(ใใJBž๔‡WมB‚$Tฤ๚~I๔แเUคฅAึ :9N’นฅฆGจกCี:ูhก4ดา(VนgจฑQGใ0ุLฝiูVแึL๘~ๆญหƒ+Qรž9m๚™]หหฃ ฃ]เผ๋'ึ่๏o:ฃ็ฝ†ฺCEEM๔ด™ฺฎ๗|›MAQAš‹๔ฏถ/ 5ฏ๛๎9ˆ๎5วภฯฆ(๘ึD/อF๖˜wwอY๐ฒi•๓Gปๅ๘๏>p•sื Uอพ‘‡r‘%๓ฮ3‰3g7%D>ภ-uำ*Jy:ฺึึตาwiFX€X๙าฎ่!ณqU\…„ึมี^HLเฐv๒Db Y…Œm๋ŠอŸํ\‘hๅ>-$)0$๛๒ฐเะr-6ข์t๔IFhko-W}d^์}ัฦOFŸ8•H(J"q9ฏŽ–5ๅ37vBฮถK@๓ตF๚‡฿ฎRŽk[••ๅf^ๅห˜อeฒ:ญ@N …m๗rm็tpš"/ืป|~ฟ๏G0I•๊เiขฃชษื๘yรฑฃ>?Z8šนบฅ0ฮ )๔Sู&ว \‹ฤ‹/>๒โซmู1;7’WoWฏ…Xตํ็‡ฟฺˆฌ6วกถ่QE5j{ๆ3ฝ0u'๚๛;‚มแั;…ยGฏ‚8ผ|ซ๖๕฿16ฦดถ 8Iฃ๙Y@บ‰ฒษ‚yx~ ˜gdุ;:บ๗ึ[ŸP๎D๕Nๅ?Œa3cท๚๘KสK’๖V$Ÿพฒ@"+y›จ็รฒญMnฮทฌแ ‡ฟ[ทP‡ซ\๎ห5พต!โรPeๅญ"iขาฐ}‰-ฤfnลฌ฿[ำ”'๖ว†Ÿ๛ฯ฿Šอ๒^์8ฌ—฿KIByDฅไ๖ํ Z‚[น€ ŒŽ๎j;๑dXQJ3[็๎ู'ๆยฺญ4‡ษธ—๖?Zth a VTA=yฬ…เ$'T63vX;<ึ{่น#G๎>z๔nžZ8\ญีด7๑ ”7Jbฮp-6๘ ”n+แOษ0ฑŠถโL‹`Er๚๛ัe๎)ก ƒb›๓ณ{SฮŠ๚Š•+ฅ&ๅึฯO)s#~&M|—ฺ–“P๓Pmห๊7ลŒlใ›dื ๙T›ƒUˆู „š,“Ž/\x๚ม3๓'ฮŸ;๓`B็‰๙๒B๒ำฮ=๎5ผ7-,g๗{?7MF๔Er5mcบก—„ฅ,๗Bk‰ จf”U๊-iKแมMsYY#9ฟfฤ๎ผ3v๐‹ฑƒcw6ฎZหQ_Eํ ~{>‡bwV๐" รUxซถL—ไE‰ํ๕‰i…Dผ$฿UXฝX&Ÿ7๔Xn\]โ5n•+WjพŽ:\„ฤงƒ 9ะBบmณ/๒„[aู บiฟณp ดซh—(ฺs6“ŒŠ”ึธ้Bฤ฿2ถ-Fถฺฒ7j๖ง4 !8p๚Aฮฯ#ท yฌ>อW~vำ๘ธผrP;ธ"Lบ|ฟ?'~AN๊ืะ๏ฤrฟ{ฉ;–ถS9›Ÿ,LZz2+ญฎืb7–*ท์ x™์–ฏวฯ/อm}=' ศ'QP@ F๑ ๑˜0กแvฅˆ๑ _‹วซW่;๚–™xจ=ทฬSปŠLง๋–t๏OุŸ4%ˆMษbBา vืใ฿Vฝ๑ฑ*ฑ?y๖Xฝญ๑งีชไ†ศ7ždุ‘ไMฺ`ลš7‘1fmฌไ_อ๕†z8q‚wซ่3"˜–๋กPgo๏๓>ฤๅu>H-๙!ส6h2ง5ยข CTšู^ฦฏ^ท†›ฌฮดW›=˜ข’ธถ}ํจm๖O‰ฺ&KHeื-dโ๐E6 ฅ-ณl ฬQ ป๗ธ Q}โwืฯ๔oยs˜]๊์|ถ#าyM๊์|ฆ#ฺ9›xo›ŠฑWบบzๅฃไsdœ.:„_ฒg๏Zผใ=p๏3L‚฿> ?์oŸŠAิ๘šC" |ฉะฌก ๓VตUSF5•ษผ8dU๊ะืŸฝ&=๛qฦxีืhพๆื๓T‡=+g™๔์'ืxฃ}ิ๚Rrq t'ปQ <™ฉ+๐NA$ueoeลตกdn๒˜๖y›เ„LŽ ็ID๎Tษ—29จนถํ*ืs‹ZลฦF&q่้(X$ถอdๆืik”ฤู‰ฌฉ๐[ิ”rK8  €w“ โ‹‹=๗ษ }ํแ?+๛3_ใฏNDว๒ัเ๘-๏“NษcหžDIฑ-N*ไฏ„ฺ(ุ๋g่ ย…ดคแ ๒่FฉtำBซ+ehžobฅฤ๕‹š ŽธลdMฐcFQ ๗‘่h4b€wี`f0ฬ ๊ต๛ร๛{ƒ๐ู้๎๏‰ฃัเ`๗๘,ลœ"ฆ๗Uง๐ ฎ๗zฒ๘•ฬๆ๐.Y฿อ ˜ฤว๓ใKหืคK จ฿ๅฉ๒7@>Hบ4”–ฦ๗…๒า“ŒY’Oชา!โณ‘ฺ„*ั4uำE๋]ยสุิ๋g๒้]3ก์ิ?t7ูŠฒbjHั€6ERเmฯkOผ๛ฎมดzj๒ศw™บ‘ฌCoพ9ต่O‡gะ<Žwิฦ_p™„ยLถARM\ฟ๙ย[†ณ*$ๆ5๓ำ1[I€X๎ศ,ดGฐ-a'ฬ|mๆ๐แึ‰ฆoฏbKผ๒ ถหซhี๘;ผŠๅ=#l…ีะ;1s*6สฬฮ9Rฏใƒ้†Sr [ใŸCC ุฐ‘ิSฎลxิธ[Kษ›rฬEโL๕๘h๗ฌgrRึ’j๏HŸบhLžœ๒ŽM„๚‡$๗6ZฆงYึฺ;ม#’ULฒะ0u3ล4ื์+6LI๒ท็ดTAZ„5๓ $!คเX=!@wรฑV‹X+yฺเ&ฌ}kRี4ึ๙lšI[]ำuถ์ศฝWวHืษ%ฆN~WฏŠH‘B์•$)‚g9*-ƒGว9l5R๚ะๆดpษ๐8gJกฌp,nนm๎aV“hh} ๚ซ๓ฺ[_iึuV{่9x“Pb้~J๔ฬktฑHง๐๔ อธp8๐จื„k้๗eธW‡ิุ/hง5า๑^?ฝ^„ ฮIว็ยž€o?e=mม๎ Pฐl;ญ7_ ม7[y–‡c?์แษMFจCmพ‰)พ๐"คdืo๋หLj&:cอF ฿štธฮuhRท๘;<ีŸธ ฅฦ๚๐T่ษ๘”Nซฮ:ฃzC…ชUญZ2€ชคT็ ฏก _ร๋ฦ่;ำชlฎ)หฺ๎ฉ*oGjsฟu ๋†ธm๔.Dฆp.=Sณ;!5L๛Tณ์I]ฌŽ}‘žv\‰ศ;^ƒOOm฿#ษ;<ฐ๚š*โผ”ฆ๖ๅฃ`ญฑฮg(^ฆ๐œนำzบ†F_ุKํื์•ฐ!ฒ}M“{ฝะj)ซU7š.Ÿฝ`>ˆioฯMฬ ง_kถ o%๒ๆฅO‰Zำฒษ+< า๕ถ>,Ioštท”OeซFนVl˜ญ‰†๕Aฌ‰ ู7gH๗/ชZV‹0จปญลไถึฃVk๕3ๆrืำดชYo›6๘ฬ'5ปฃv,ฺ-W„๏… ฮๅOC#ฎ%แผEญร๐{แirt6j์qCญM"’ๆEแIจชอน Ÿผ9v์‡o~% J๋ๅ็ วๅ ธ‡ญQโศi๓ Y{๒ ทถgI\(~X ‚OžฮYกƒoคิโvkMnWRณ›ŸCn"…วะZc๒ธ‰%ืWlึศ๘‘54Ei>`)‡a„JตP)ิ๊ไถ˜\า8b่’˜๑ฌำจ‚ณ/8(*6ผ,:ณjต†lึFQมKฤŠrฟจ CƒO๎\ :ง๘Ž้šajบXษ4Kธ‘<•ผ hฎ j€h~๘7j่]งฌe&`็V5๐EJN๚ต๕<’ŠศDศƒๆฉ*ำVศฉ9ศๅd@5ด1kI“ำ7gmณIN๒ฐaW‘wญRจ๔a.!ใ p’ฃ)aฃ< IBป-lฮฃŠ Œา)\*˜ว y็4—จ๑<ๆEพ๖$=๊๊r~|iXฒ"่ซq]Wซy>RŠ๖HaJ ๅะI์%ฬฌฌฎVฎI/๚งQ.บ‰V7mc5บOJ”ไ‰`Rห„นR*ญฏ_นยtQV๗=yมvม„~ะ๎?Z™#ข’eSr97ฯ”GฉTMKnF€( ฉ6m๖3Chล=‚YOฦ๒๓Y๛vo–>ฑุGง!JJฐฐya,™ผCๅIดดฑํฯHฎๅh,IไYrmL>@o“ฟแU]i9[(๚Zจ\]ฅนฉตaš,—ฅ=k`ปฯ^—T“ V๊“รห'˜ถrb“Wศซ+ๆJžŸ`๚ฟ5"๚†oฦะจ‹ธU.ใิ‚žPs+dฃNธ๋„c€ว2˜ณ™ฤ้ิฏ\1อuๆP˜˜AH๓8๖`cฆŽพ็ลiก๒ิ ,Hb~ข^8ƒo &ซ วๅณม s ยไq;ภ (‡;ญฅ™n?จฝฏ1๓œ“ษ•p_๏H0๛ใน‘.g__ฯ.obx&‹—๙‘พฏรก๋ฬอ{ง;4๛ุDศใE9.sฒn5 xpƒc-@n'I3Pšcr฿> มสzผ~๊ะ๋lหบ›่ฏๆNPยฯV1aŠถ3^ฉฑŽi{yD] ?,ัO€ฯl–Goาถด{9LดI๎\HQฦIV7ŒiC฿ย์LฆภT ื&ืBT@ฮCฎŸƒ:ฅaN‹‰‚ภผ๒คo‰฿ฤ ัฉ@าHŽƒpOบg๙๑Ÿ ๔ฤ{๚Šู็€N›๖ห\ <^<ž๔ฤe7Jป—เHซกำะs้™$y›šeข๊cyXสHปแ~„า ƒ_ุ`a#มaฎ้f%รMNxB้Kโra0ฝ›:;6ผbIึaใvฬๆ:Oๆ์ดZํ*Igt๘>:ฟฃhA^๋ห’ฮ^nฮ๑„qญสiุ ƒMศV'bฏฤ&boึ4\ญM๖๐Dlr267 ˜ธŒีUฎอy…ีY^Šฺot8›๒ภf๛๒ชนyุ‹(ษ้^๊>ˆxย๒ JภŸต“ฺ[k…4=0bุฮะฬ0ญœœุ}iฮฝฺ๘พๅฬqwv๚ธุŠRคษฮqWชXณีVH็:›ŠMศN4t๒@๐ฒSน๔ cB;‡เG๊–ธ vVNoญ_ฝฑวcฌฟญ–•๗ฦ8;ฑฉชBž๒กaฅถy่ฒQ2สํOพŒฬฺๆm=P•$:ผI๋U0ดfฑฌฟ-ปฏTJ๎๘T฿ิl šฒ่”_>–cรcป'โษL$qh&ฎ&ฒSะ:*B๚ะŠE[ฮฯp่P…B"Ÿ‰dบfuวรวลึโ\‹Zเ/Wi›ถc•H†Š]๊๊ลบฐกถG†ุ?Xศt๊ะ๖่[Z3—E;ํZSGZต๔ฆ๖ๆ๒ฝาž'_ฅทYทธน†kตฉo€ฃŒนํR๐๑ป‹Eผ[/™pท™ื7•๔tขปถ.:+Uur:o"๐ šKฅ…’'>งฒ๊f#SGคFIศH}ฎ)๖ศ๖x_m๙V็9 •!ๆ^ T5ชFะ'ขXŒP฿.iจxk„ฯYjแ=ข(“ŒฟซไอAaญฐํจ๎Aฒะณฝšล&(yn ฃ๑ผะฉฅ ๚พ๑Hฦ`Ub:* $™ง/้็—ฦ๗้™ณโถ้ ๊Dธ.V™4ท$Aำ:)I [ฑvfnyS}jBq:ี„™_ทฏิฌPmSฉฌQy}฿J0uลช—ุคตF๒dŸvXั›ฆ`ร : A่Pb#๏ำšŒน˜"•Mj ๒bE[ำ8๚Wd(ŠasSBŽัอ๖)…หฎ“'1ณ6„ะบ•ซG@uดๅ†S–(lK‚ƒV™i*†ถ6&Bษ!ัh–ห&ฅ…QรฟˆŸ๚่4ฌ1ล\‡9ย(iD‡–c‹ฯ@;qำา);ํุL[Mา‰\,ฎ˜OภๅL(จK฿ื8~ถ฿†ๆšx…๗L>ฎm;ถนŸ|๑Rภ]ภ@ฐhD2lใกSW"™ชŽuภพrŠ็คPNC›sฺ!7ณR๓[dtesŽพ&ฆ๓๖r๚ล6uqๆ‹,lชต/ดYซ14ผ มฆญajXgWฤ8.t?$Aพย๏.‚ีษ=~‘™์fŠV „œ8X$›p…vEีzg/เiฒwfวแ›ุŸฃ๏๘ข๔.Vd๏"Oฝ๑E๚๑RJe|ส(า๗Y*ƒ~๚EJ…x/^๒ŒRy!ตฦ7Nโ '™ข€74c=G๚#ขณtณ e`Y1SืฟFRท…ะRkขksฮ‡sฬjKh ›+Ÿผฉธf|ท7ข”ฎฺฎ4ซะ.\4„นŠ(PjN#Vs$Q๕KผX˜•M~hฃ๚๐Xc>[‰šธ€Nบ ’๘Yบฏ๑u6…ฃ?ง3ญ‰kW๛฿Šฟ(5#".- ๑ฟ3ถm^kE•ใhคIสฯŸฤตฃ(้ xœu`rฦ ปย/qฅZถลNช {ํ5โ๑_ฏ@ฝ8YซneB`ฑซWX9e Dru ‹O›฿œyชy1zaฝฑก๋Š๐ฤขLัึุ#ฎTsŽoส1๗ๆ$ล_ดั/็-ะ๒:9ึ„${๔‡7ŸTตขYY_7ท๙ŠNศ„฿4›rŠ ๕๓‡#œ๙$้wะฝ"ตqๅy†ม๘j๛ฮ_a๕s๛WFฝ‡=่]ู/9ปXYRนU์v>a์าซพD]i “้ |ฉ fึ]aฅHFโ๙๑u‰$ฏ”_ำงบ=ฯbํIA6จŠ ๅ[ไ๊ƒฯƒIำdุj(๑ƒ๖น ]Y ๒S˜‰š…$f็žอัดร `dํGฅ€R mู–โ2&‹:–๓ญฦwฮ0}8:xท™eฟู๘ถฮฑ"i\yPฟนร—;vฆ๑Mธวุ่๕elี/ลฒหm–[งlษรลfžิๆ3๔„–ะ™า’gื๕ภศM(ุุ่ฬุฬ๔˜b๏า„eRŽ๘ษข’b๑~๎๊๑OLHอxส|ฝŒด๊“mฎK2]€Qฃklฑซื.”๙฿์ญ` ฯ8ี๎หVomb•nญ\ถ yก9Lๅ๓๓่@จ0ฉ๖ฦณg฿X[<๋ญ@๏,™7 ภอfๅK๛า'$InJฬลต<-ฤธ๖$๗ุคX8Z‘— mลธdกzพ1‹๔Gๆ'Fำ68Aรs ธฅฝ„Oฟล/,@<*žด๛Rอ๒ม ๛@rMุํลfq"ใป๖แ๗Lๅ1ฺ&wSบ้mลญ ุ1ฎB\Iะยฅฑ+า^X ‚…[๎Fž}รจLxชOU๛~ฆ/๊c•2Žฌ๘…๑—€{rG:<200rHํk|๎ษ๕ฉBœ ฆถ_S;๊?๗ภโ IกฺฐถฐgI„cำœอ’ยl&ไ๖๏_R็ญkฆeVlTq>ซร|Fำƒ”๘S;G{>Kถฯgbk  ํ 8—5๊˜Rฮ‚\\ล‹์J(.นšฒP7ฝ@Hล|๙C‘d[–9ณtxๅศฦ๔แรำธม1“ฌƒ ๛‹อุ๕๔์;ฤ‡จต;…Uถ๕#I#ฬทไฅ€ไฯM|d+Sน๛เซ๛•5e?&,O.†“a๎‰'f?;๛ฤ"n์€ซ-+“š;oู฿’y*+)า\)0w P-๏ฟxqนxHNืฌ’q>rbFsฬ๓ž|#kiV6๘ฌฎb„šmW†2ฑEaฬถ_C์Ž_@ซiฌ*ูMgํ๑ม—–Zqีำใ5ั,Qzๅ'BกขขX!แ˜i@hทท7AาUzwf‚ัม`& d€ำm๙„Kภ~DŠช+ตaMf™E๛Jภ~5์จK)bาภŸฅ4ฬใTˆ#คไ_9†ํฤ/ษr`ฺข—4h&[ฝ#4๗mlRAฺX—”c„๛ หฒชn‡s=zแย(n฿๗}”_fF;ข๕๗Ž๗dZ๚ํ^~Ÿาย๋T™T)2ื|QฝฌflŒฬšƒG๊ฑ[b[l๗๓า๋ค๛ค%ษถลPg1X~ฎฝz๓๛,l98ถํฺํsน>k฿Žฯ†‘ฆ๎uvv+ถฝบ`หC [๐ส2œ€mถ๔คtดิ…ทˆฏ+…™๑ฒ‰I‚๎ož˜T:1SสเN๚^’zšณต้ทว`ฺ%ํ—ŽK7ภLu›tด•๕™ถฝ: Mด[ญuv+ํ๐ $ขBซˆ{ใO)ค6 lWธั(q-ท ๖1หฬไฌŒO๘ชB๗QJ๙…J˜ืe5ส”ท$ƒ”Qใะ|€๎ฝƒ~๗]†)ZสKรˆฝ"น-Ÿ่$๕ฉ›ค‹าาช๔&้ ่ฉ๏–#}Xzy/%8œ5๛t‡นฬฆk#tf๗$๒H้ฒMDฒึU•gำฟCำตŒ๒sณJ–^KŽRๅ””ฎ   ฆZ…ๆธbŸ0˜j+5 Qvหเi†-1์๖8๚}พฎ= ฅร๓)hผ๛แh๓็‹๔พ าย฿Yฝททณa๛uwPžฏB›t๋R(tš฿ ๖w4/39ฺ๘T๔ŒรีA•ูำี่†๏๗ภ=๛ŸกKะw ]๘tๅ<ไ๒|ึX_งป฿ }!I๖ปkฮiiBสงyt’็ร๚.้~˜.KOJ๏€H›๏—>$}B๚Œ๔’$ตYiyฺž^I๓Œฟ๕Loฺg๖ศU9…>R +žM/tฤฎ1ฟC,& Š}ิ๊^ฮย+qะ๛y1Z‚ƒํ>?JทŽ5'ฉ.7vŒ๗ŠPจม฿ผeฤ[ฆ‚๙Pบฎ}™Ž{(tข๙–jย€„๐6Uบ๕9ปƒ4 ไ ช๘U:บ ชฆl๙ฝ`๋!Dmำin,Mดด๊pฮฆ›4 คmฅP\๓๕›Ÿ~๚ๆำ3ปvอLณ:<8xืรw า คณสvฟฉ]x๚้ รฑ็ฮˆัo:‚ะCม็|8mูฐLAโBํศ๏!MŽAภโ_q)๔๕๎;นฏื๗›๗ฃRซ!rุ๋U7G^iาk*Bเge๘ฯQr ฮ2ลRถขมลฬgไษ{ภฏ๏?cRฺ็ค}kฺ„$‰\mษ๔ว5kฉๆัษ‹‚ว๓ฅล/Pใ?รˆMอ%{ฃ์cpŠ=฿สDRณ:tfษ#๔Gฌ a)ฦั„็ ฎ#›ƒ‰4ˆ่>GZ[ฐB•ฆฬe ฿pจธxR๙โ๏š<˜xเE๛ดkแ^ฯฟวำ้ภoยำ๒ณฉgฆ+ฯ๘^๛ลภภนQุ๖d—็งฯLปm •%‚œบลŠฬfNฟIฏnfต$น‡Rฃ ฃฃ ฮ๙2Mไ ฟ%พ[b‡ฤ‘-H?ญ’ฯ75Nr^ิูVsHW ฤ–tล๛ž"mเ5๊ฏbกWu๋Mขณ‹UใๆฏศGา.Kh€ณž4Tุกp4 †๊cแง7—| 5#TผนฅะFQ“d๋ฝnl’ไฺ5อ=๙กlย3ยฤยwcUหฌwฅตตˆŸดร!๒ศVj›?LiิHไ DฒšU"‚ๅ=G7HแHo4mูV๐4WŽ !ำฯTฌำŒฃ‹ะeVฤซบN'๕"Sแ๐"‘‚$ฉ‡&ๅถb™ฉ฿Cmx”$/{u› ีy์#*๗็vช–ศ y๙ ศYฬีถๆ‡บฦ๙ซšมคาพ/‰3‰ฝชWOEํkaฟ$ดทMSฬต8หŒภaŠ๛ŸEศ-”Žpฐืำช;A้yแธr๊ก}ใ้žพžำ—.1I S๙็๕tๅว๏>:พ่๎ ๚z๚{“ฯฟMฒ …[ณคaRQ ; ตP8Ÿ}๐วฦ~Œ๊ํ=8‹}้ž_‰ลฦยU†ซ“_•{พD=‚LฑdZ1Q'ฎ๚ัcN€^B‚ธบŠ6ำบพŠ'ถาปf2EoิLŒอูฐG”๔ƒ@๐โฦ๐<ณภมด๕GOcof3IJ`ฃฎ8™ล(ฎู&พ|พ…ณ7rแxd๐๎'๎N๖'เK๘๑›1% ?ถ%@ฎฎญี‰A{”ฮv|„๖ถื‰83}R˜์U6ฉPraี* ฺ ไ›˜"ŠnฺžZฉQฏbว็–๕$ฉโ6^™*ํ!;๗.ะKช•—aีžฅผM\ษตež”˜=์ *หงฃฝŸUpขษฅฮลPpยjดkT๑Š-I฿—6s vำ^’๐ฉ`oคn๗เฺ๒ีฺง7็๗O๗œฬฎD๏Uส–‰oq็ทAV๚dง^J#Vg/ˆqํย็hn/`๘>MKsŸN:Ÿ}์ฑฦo1ฅFuฐœ?ต฿c%{คqยˆ์4–๘ŠูYkQMดฎ๋œ™์UAƒไงตคI˜๚d+žๆq๘9์sšqทงQซVอฟŒ˜qฺดzU‚๛5ฅg9œนฬ{ๆ dWšVR`๔ จHlฎไ’]~u๏[๎ๆnูั9899F‹๊ๅžฎ€'ชIุ9}๎ศฬศ@ุชO•๊ำ฿ฌ,NUธ[^งฉ—jฆYSฎUฟ:ล˜†๚ ?R7WO๖Pน]บฒžกJยอuชชทซ3จ๔tM๔๔๖‡๚๛ƒฒฺ็s๗tฐ2/์7ฉฺฝ}ฝ=]#=ne—ƒฉHะ‡ืgก&7CMŠฮ…f3รXŒA•ฐ฿๓yyK‡กOฅsyฆwน:ๅ้๊ ห]J—๏๖๛e6ะำำ7ฺืำ3ภFGหๅ๑๘Gw-๔บฃปไ‡—"ƒใ‹๑>ทวใ๎‹๎ดN’ ๏€sC6ึ้ฆิทฦฃุฦJW๔bc5๗ะ*‰fL8;ื๋–H(ฑ5G ƒษขญ์yžT4{์(v.›จ.bgฝบน2E^‘ พีต~ฝ?ฌ•ถึชdWHจัลPษCš6Fbฅ`ืj‚ํๆ‘2อฒTฏ@wwŠคชฐถn`ลtฌมš‡฿ฅ"Zš|๒kUp†ธ8๑ํ!“ไ}—t€ทHVhkg7ฬ๗ฺ>ะB4h`\๓ัsv"ƒ0= ดอๅGืmฒง ๛ณ™n‚๗ปn} *Iƒบ๙๖๕DK20ฦ†Yภษฟทฺ๑จวาb็”"฿ pด5F}>ฅถี>ปyDาl#ิฐDFฒ†A๊AH6์C‹›ฤWลJVœJ๐าCCรฎฒs"๚lk e +•ฑ'eฆ™›โษWื6sฉ}ด้$•๛ž๙qnQ฿†ทtˆ?ฑ.ุฤฉ’ถูO@‘อ๔w๕ุ=2–ืg\ว[`ช๋๐x*ทีcภๆ)ู๋๑ภฏผฅ ‚(า‚‘\9ฑ|a๕‹Eฯฬ๔ๅๅ๖NV,Jฬึ-S,š@X๋,ำk อ\Cฑยอ๐ษูถึ\ฅูa‘ฉ\ฏ—wHใา8๛&๛ฆeํ& ‡T@๙ไr (r”!อ5 k‰ุ]ู8m€฿„ิ,œ็Cฒืฅง๖?5ฯํ{ำ G๗ŒC๏๛+žมุ˜็บยnไ'ฮt,ท๏–~‘ฆหๅฬฤR๎“ิŠ”อ[เถ›่พ๗ ๗!8#๙ู๏ฑ๏ฆL“๛—Q๛– ๓@"R็ ฿่#081FOฬกยO6C*2ฟ9.T~b"6Œฏ#54”rx; c8ๆ=NงGvด}S%yาM™รพŽลd:ํ’{<€:Xcžู•N'๛™หษ˜ำลถ๛฿€8‹w‰ถฯ’zนม–ขฝ †2ฝ ๛ ํc“ฑษOว&c๏ไQLs.ต,\bฆbšoƒS๘ฤ~‹^…[{+œeš๏‡Kเ,gIjˆ๘๒ญd9(—` ษœœsๆS์พูฐ‚ฝc‡uเe+ปํF ๅŠตkRำ๐\แถ8'1'เq๔rcๅไห๋ฅ฿cงขn0V™พ๋ๅษต ์฿Žฺฉฒ€/5ภ ฌฃpล`๕5สฝฃ)KโX‹}RะฅuB็ตใฒเ€ kษํ#ษ้หN$mกตฤ’@y๊b๑ๆT„หนฒฦแXLถะ7ฎฃล-ฟ.Iอ9าถY'TŽฦะsdฏA[^นŽฟ;ฝU๒Yh:R๎)ภHฟO+†พล'^ใ๐7[Sƒฐg่ค`๒[+:1CœอŠ“NพŸ6?n๏ ่`ฺ๖ž๋ลฏ๎ฃ๔แI Ig้๘[#ึำืaVฅณ=aJIŽพศใำ‹ฉ๐-๔ ๘c฿b฿ย่ิ+`ๅ#9^๎๋•ฯ‚,•Šv['๔xฉ„ึ~_ะใZ้%ิ{lYมฟA#@›s‡ใ-ไvต„šŠkซš๔aาึฃ6ะcg1)ฦพบ๏ แM!_bคƒkยž๑P๛Y".ณ8#4‹?“q๚ศ฿ษๆ^:–H่๐๑^{obด;w`๏ภ2ะˆD`€ฝrWใk๊Kว๗็๓ะ9/W๎ฑD‡ขฌจ๗บUžะ‰iฺ˜u•f'F˜vตฐฯQ ่๎A*‰าี…๚2•ž€jผj+n€โMฌD'dู9'n(OดSท๊&lํ๚E๊คศฑ$)&E•รmค”ถๆื.[nฦญ…{f{Bn4"M<2ม{l้ผF๎Šเยx้43้”ŽแšภUpzs‚f:,IVณธs’ ฒŒ6M$่ณS(J t;ถk฿”v`8๓=7ีะ9yo@‹วu}! t8›Ž^่vฒŽa=t`ธKcสพ]qm*Š>ส{ฃรณcฑกา‚ฎรDธ‹w๙$ัคตrVทmู๋Q๘ r&-฿BDsล+ณ๓[จA2ฝ]จฟงงvอเค `—i๗#ูทKฉB๏ตKRฉ^œ ยื$โหฯ๋ aา‰ h5oๅิCบ#^ัพ๑ฅmbCrz$ที5ืEST3เ2ัฟ#ไ็็˜„›!•฿ฺ&QZ SYkeฅมˆ!พ\7ลDmFฉb*žร1์\ !]rKRญฝ,Sƒาย( ุฆ<ข์„งโv-ท^า+b๛่ี['(=…€คh๕$FAเdbRฺHัฑ ๔— ๙eฑ Mzะ€ข้กน“.&ฑ ํฺะ  ฬk้CMจ4ลฅ žฤsž'†|K aผ6VญN,-M ŽŽโ†ว‚๛แŒ™)3)ด๗โRftw๖9ุ2pฌ0MTฅ๋บเ?@{b)j.kmฬ,ฺ9‡ะฑผ ”8ฺ๓2E–mฐI–@6Xำ2ุ,ๆPp „Xญธžย…ื'Y๖_a฿@๎ศ๒hŒๅ™๏ๆQCv๒ณ็4ํฆŽs…ฅ –vd๑EฟD)da‚$UฆlRM๑\Jif๙F2g7Ÿ\๙ŽB ”*eฯาเw$ŸำV ขปdXล‘็ฉษŽ3“pแe ๓tœ;งI;๒<5Y‘฿ภฒut)ฝ“™ด๛ฟฺ>ม๖(๗ถ“ฤˆฏ5I Sษ!Z lตํ6Yˆ *อvณะSรค ซm?ฉ…@CDไ”Bฝ้ท}>‡kๅ7V\๛`๐ฦ.œ;{๓…sฌฺำ๘G*ๅ่aปi_๘/๎ฺ$I…Œดช6xXDe"˜\’txม๗s;6็W<๗“XNนYด} >G€[ลก`ฏร3<ๅศฬํs„†!Wx,็g>z[ภำ฿๏ fอ{Jฝฝ%LXตฯ8๐๎gŸ}ํู๕ฆ]=˜|฿(6!ข™‹ฏZศ(›U ๊–y฿dาYcาศฯWZ๚ใ๒ฮบšHT7y\ซฺธเญŠd›D>8๋อ4—Y็Z๒g•€zSอ)[หั์|ึ:ฺv=า• ^Š๒*ฐO‰|O>฿ำ_t็Jwxโfปmภr*Yƒ๊„ฉ%BrVBR-๐y๑YD› ๒ู‰(ๅส6Toท€xญv`ฯ“ธภ ncsะ๘ŽDŒกฬะP†s`+lƒฝศถฺšB ช.d็Eฏพม›ƒƒ&&ฬผ๙~็ยฑc`ร|๛้ U”…8ฒZดฦ4b :lึ 4y&^๖ฑ……ย™=M.๋๚๘าล‹g๕ฦo|๙ุ›>ฑฐ0๏ืŸ?|๑–รฟ_์y๙็ฑอฮK็ษSฐFU„ขุ’ypžL‹ก ' nณ‚h฿๓็๗M๎;yวo‰ซฑ้]๎>ค๎๏w้ž!๖9๏สWผบ๗่ํGฝ็^wGใ‡โ~ท๎๙กกXz๒u‘่Dส,B™Ÿณฝโศำ—$ย2—งP าOห2*ฯ^น พ)ฆฦGฆ?จป๑ม๓w`ฐ*_Pc“้^๓JLฅพ๎ฏSUHํฺŒ๘3Un๏ ซๅ๒6]์%‡อ)z๑๎่อKะษ#ฦฉฎญ?ั ฿ฬ€4อ”:yz$ค JpUX ›Vึ!gต(q&†ผๅ้qd˜[ย”{๚>ๆํ์ZuF>แv5พฺื#ฯ๒ฬOG_ฒVt_R๏Œ0 ฟ„ƒฦOห‰พงIkฯhd;ฮeฤ0ๆ ัeฦ๒ฒ Ÿฒ0‘eอ์หหห๓๓ฐmŠNธu6JG{ถO…)๑‰&4;„<ช#ยฟีฮ๛DฉqตดLW%Lํ2"บ:Iเ(Zช"˜)ฐœŒาtT๖7้—๎ึ@ฏข#?บ๗ŸุJุำ๘ค*ด๗—Qฑร๔"tถฺ๛‹ฒฺxIVท๑ซkง5Fp—-lฒMd'Oฌf.n"=FOพdๆขษัฃ฿โุ ฅฏ( ๏ยถŒ! \ต็ณ@…|ษารJ0ๆฮ…T๐๓9Bƒœt’๙ !=' Yฅ h”)ณ@งฌvzU๋๗z\Ož „ฮžcƒ1ๆpธ2๋่์x{ผn๗ธฏ—฿=–Iz<=)ืะ|คฆ๕๛ว๏๑่ฦ_อ๘{=s๙wขฯื๔ ƒปฦ๘ฦผ๊t;T8<ฮwWgWบื•๕uฦTO‡ำำแpzปีL+6คธœปำืคฏaŒŸ›ฅทฐ/รำ/HทKรG๑๓ถ"ฌKxถ๖็“๑.r้%ƒž9.E๛ขฆuก‚?gฬ O๊a]ูOX๐ปGC~xTู7์็๐}RPOL้]~m2Nt82‘Nง6ํ๏หh,ฉ:]N'snyะ^|R(รำํ ' บฑ>-.YAo็ูค๊๎[วDภ=rŒฏ–:้@dพฦQ0˜QŠ‹ฆPืะ๎๔๔Bฌฏ็ท5ไAŸ_ฮแภวาใ3MาIาšๆz–fTŒ)ŠJ๑r8‘PฬมMเธ&ฺ”๙เ๙ทณ'ฃ๓ณ ง/%J]ลดDxษ็|k"ถ๔A‡ใ}ou๚.-ลNภrไขqW๗๔… ฟ;ดไ๊ซ…๚<,OKมพฦOณณธ(ฑG€Gี๒Jqย‹ๆJp•#~ `# A๓'๋จ`2‹บa  ขHLcจ้6M‚0ƒีC‡5Iฺ$™ฃd[–‚*dKฺฐ‡cบ่ด# z‘Œw๊:˜šพฮ๕:L›„ฏรฎCŒMD]2ฎ‡บdch)yH„อŽโ‚วชpโู `งVชkล ุ—V‹ `v4~ๅซฅ^*—่ฏLQ๊bใ“ธ.ใ&5ส&ซ˜k๘ณbฝฒ&ญU 9ฑ‚8 |<-4O;€ “ัC๚!P_2ov<Hdสู™ฃw฿}๔lฎ/’N4ึร,๘Jํํo๏ะ็`๎5žyๆ™žo>`OขฅN’mฐW้๙š๑;lา,ๅษs•)v%K๏ห.ล21Œว)ึUjศ0ึต฿ฮฤ2Oeb_ี฿้฿Bืชิn}5PJ๎w.ห’N™ส๛ŸX^›ฅ˜€˜ฟ=*ะ`T†k[ง [ŽOB1oำNXV†ฑU‰ัต+ฦ“p๐0•฿ทญru„T‰-ร p๑›บ8ฒ)Bi‘ธ}ฯฮ๎๋ีr~~ซ๏บTญ&^ึqฝ‰ฎ@๙sพ0–{ฬyึี๖!ํซ˜+ฦ+pฒE๒ฐ/*๕๙ะX xฃ ’‹O€มผด[Zข^|'ฏlภึุข –Y:flL,ŸFdถ ส๒ึ5๊‡๒ูฆใƒ”ฌุQถจม_ฆเฆฃ:)tืฃ}sหแษ{๋PEขฅ/Š\p4›ั>Ht|} มโzbฃvœ?AV>plร๖J๛y„œ–ฉๆVะ-งต8<)ฯํ \๔4ืรVกศK:Tา!ืu |ึ๋Tuำฎ9ซYlชTkศ„๖์แ‘Š&ฃ‡ะ้:>ต‡ˆ—ฑษ?ฑฅ !แXY์H๐ตอ๒W4aI๖ณ4ฤYŽ0ีœ8ยะ‚š%aY Kxบ๎!ุุHืดทม๎]จไัJqธ:งŒecO์cฏƒใ[๖]5M!ชZKฅฅ™m<'Txีษรnfep „Z {R฿o(ะ;–฿7แ%]_] J v+Yน,๋๘ƒ็—๐ฺํ3๏\…ป๛}{|พฑ„>แoƒ๐ํo[ฑ*]ทพฉใŸแถEกv4~Pn"็คšGิ~ƒด ?Nฉ~แำภUผ@ืบ!๕…˜๘งง์ฌ็†‡suภฦม aš ถdvซ6HฏeSํตIซอฒ“อฃj:`AN\S*ๅ๗)ecใพศšDปqO๘j˜)+_*๓eณฺชEีT ์๔ต๛cฒFŠƒY6S pเMt?: 0„๘ะ๊ {8"Ž/ำ4฿•  ำโ@‘GูL:ฝ’Nฯ0Xฃ'ฦŠลฑ M›อše&b56~((wล1i๎๊ƒ?ฤญฉ”๑qEŒFaXนล7Fœง}์Vสโq›ŽNล๐)$ybfy/ฝคbนŸ๖ŸพP]ื$@œoฝฆ7ุlb8๗J\Otu%๔๘+9(“—ฮ๑†์"๔ฑ@๊>หผ ”ฃฏฎ^!ฯซนT*๗ชUภฟ๑ณ9$“^%)เฬj„ฌyœ`ขHษpก`[ง}}ฯDv\Ÿ่ž<บ4๊Uœ]ฮ๕ฬ =.;ะ๘OU|Q7๒ฺฐโ˜บ๛X๏q>ๆ๘D๘E’ค ฆ,ซจ H8 yxช๘ํชพ|B[[ƒชV™Qษ_N$%‘ธœืส—ั่rYjjญ*@e†š>ƒI"syOSคสQ„๊Zฮฅj†มŒฺ“ฤอ4ฟ๎ฃษู่ฝzdB.…y๘™Z-๓พฮฅjตTU~hจRบด๏ษhฅๅ๋>โY{iๆงธ’ “4ๆ“u{เ,Žฐ=ฝZK}l96Yพศ*ัลฯ.}&Žณฦฅ่™ณ•่“‹ฑa(`โ๛”ษฯN76โ˜ฤšํ„บmืXฌ&4Vuปฆrnฒฬษํ์็"Xื[œqหQ‰ltœpp†2h๊ะ์หZ'MฒqรWCšคพy jษ:ศ๋&ห‰0†แRSNฦW๓๏4g๙ฐjฝrอ๕ฮึ€ธ m{๘]yเ๊ภF$ะ็๕&n์ว~ถฑ๒ M ๓s๐ LืฬะACiPPms$R€ฤ-ท"ิ็MiŠ์3HJ๘b๙$+*JH/ข?ฆ^7ึJkำ˜Tๆpฤ!ลPBศตK,dmGฃŽzศbกTCิSฒ(Is&jฆ,{ฆฃ๑jวๆ2‰พ>รdฆ๎่[๏p ญๆีL$ฐ;8ฆGุกฦ“}ŠาทIHV๚@ฟH–%Ÿ์ย•<’์ํM†.Duu&Bt๏p!†ขซ#—z่ิ)Hฌu่eKš•oYศ„Vช๐Q๓ ฐŠฬ4ุฃค)›,ƒ๕Kฌ“_7๘ษภEง๑แ •้>IตบุซพB/2R€ฉท‚ฃฃ.ˆ฿า3โ๚?ndŽua[pฒœšค“ŸN†ฐ%ฤ่ฃ=dชฉmเfž๚‘ ำแsž๔…?`๎ฟGร=tƒฮ ™9{P๋๎pปŸฅœnเA:;๓/5๊๔0ส9~ๅuฐ˜Y_’าช ็๐ูY๏*ใๆบ๖ฆฦ"+MZ ซง5ใ@ดฯอl%Žฤแุr”V๚ร๖l{่ฑ๐์‘"๑๊ฌKk+ุ๓5ฮ_{<|t–Z)nฝ)?๖s2ู ย”;kAง/=xๆฬƒ—X‘žแกๅซ'O^mŽม าXf้)๒)|‹ฮ“ฃฺฎฃq:~2า฿5—1๓+ดจXGฃ๑e"ฒ/ฐน\ดฎ“dNตEœี7rg…cฦgฺ่=มฦธ…ฅLขCJศ๛ฺูŒ%์1•Dปาk7ตŽAฮ4ูi*ทหa๛|W|#พš\†A๑X&‚๋ฒoฎยล~d๗IŽ&Š"ฝfลQ- V›EศŽห@AคQd ะUฅ*ผ_“™อีŒAธญŸ+ฒสใ&QS$ŒชNOฤzใณVxšfยำิJƒn้ Zฃƒ}ฒโSŸ-hA-˜้ฆ:}#ฃ8๖ฏิ}จntuฐศmำSnืฏเ—ฃ#ย|MQCZ=>cแซffUง“CฬDี ตZF๓K4ๆ ะ ฒโฟอ`—IึŠ,/ตeฉโŸ่  —ห@9l‚Up 4๔_โเ%น„yป@3‹mทFo| ๋kW#๐-f" $5บ๚ษีUXล0ฌF$๕-ํf๋์W-\]Y%"4ƒ‚tะŒ-‹|ฝส๖ฦ?๛ภbั็วONvฤรฦg/†รWXงu}ภว;&oˆ‡๛ขรGย้_ฏสถBใ:šฑ–ก]ีข‰์ษ†R MDฏ๙ื5>KGlูภษฦ„โฬ\ฆA๘๙ ดZฑŒ๓ n 3ํeธ$เ_ฦ{ำ๓ฝ๐฿y38p]็^cอQ3ผ}œ vดW๓ย้ žžพลฦ{ลuกJง‡9<LNฟพ๓UคŠษ7 j.X’e4ผ++ร ๓ต.๏cๆ›œ”–ืฤbฃ]_๑:ฦ&ฦY;@‚W%แ 2f ๚`2ๆญ|ถฟ“ฅLฝi|\^9จ\‘ํƒ?&ฟAŽ๐sN‚œ–ง“Vเ^$‰ฐ๋3้ล๗ฟฟ—ีO๔F/๔cƒœฌธ8๚Nฒ฿ยF๏EyาAินฌFเ๗<~ฐส‚ˆอ0้5ฦ}ฌภ‘&๎{J‡iYrศๆญu!ƒSeฒtๅ็ฎุ๑ิ๙๘ธ็ ฮeฌ+Žซ็—โง์ํ๗ห๊แgใK‚V‡jษ่ๅTฎ0KŒ,7E๕2฿—๕•ผŒWำนล{1ŒNt๐สp๛^แš"3แMเๆa์๐ธ IŽะ๖jPเ ฐญ๒่ฤาyWซ‚9่hU_Ÿhm๋“฿ถO7อŒ๒ภ `dt&™ฬP๔โบขฐ’ข4ึPjยLฬ{๓ๆฐฅƒdั฿ณ฿qzร|$า(D™H๋ @vวุE0›ง{z$ฆ๔4{ท>2hŒ vwร58b๐มU4^G*ฮ.Z%฿ฦ๔3ฑ ๏@lฦฉ#์ x'`ั\‚oะๆuI ‘\'‚3 ก‘’jถ%ƒ•I0k vSล{pV{$:Jฏ2ด๏่d๗„>qœใป†tๅ๓ฝว๏žr(ร9=oh/šฺ+œ๔FC—4ธW{ำˆฐH’s›†„ฮ‘บืถŽ aŒ๙hไฃ5 ฒ2ฟ)ใ%Lี๎๘้ฃ๑ฃŽMูฟฮ ฿x๘7๑z{!L๐J˜3•อๆYํ“สย#z/(›kแ—†qn+PG˜งฮ@Bฌสoฟ1ึŒvฟX“๚d |–zฟP{Mšc‰ขl‰‡QRจ(…`šไ๛ i!Gั†XอœYŠ๏=sfo|้”p\ฏว๐–Š rgs๓D+ฃA3๎Ž…wยŠ๛ตช69œž$SฝXกQZƒˆนร3ŽZอ13\?E3๒o=hฮž ฿3?Z-๊Ewˆ฿\‰!5+ๅ—‰'๚n )้h=๊โึุ *[คŠวฆUdัq๎ตฑฏwGํK‘rlฮHฦ![f๎‚๋4ลn$สๆhถ๋ญศๅ^Aข*“า<_ซm?˜ ^๊ศL€I(ิ+ˆ eW`ฺ3๓ภาโแค:0’ุ“H จI—>ู(M๊๚KฟXฏwอ๗1tŸา-P“Dย;d“}‡&… L๊๓Wส9ง[”—ห–LyบEUกM,ศ”M™29จ[rๅ%;หเ6g‰–อs(Zึต3ฺ4H—๓๙ ‘2๏(ย฿•อโe]'stคฯณi๎" กtด ฬ5‹†^H’,าช#8+ฏ฿Feฟ<q๐ข‡ีฤกEฝ๋G๚๖ํฏ—ห+Tp๏`&ัฉiร๎๕gวโŽ9bšไโึE–|{Tšj—qgHศ-o’qภž˜„U š?น8‘žิวป'Žอ๔ปย๒%-sอ‹ี|r๒ๅ|}r(hCรA็ไG๛ธ์ป๑ลt~>“ธ˜€$uxmํvB„šต6‰ุpŸณ}ํAUZ'ศ/ุGคM?d!!OdIbvy›#›UhฌZ=สํ1J้_QสtH6pฝฎC/ำYีฮo <ฟuฬ๓ใŸ=”V้ไฃ”โูมโ}–KœaAๆค:uไšq์๑ู+‡3s(ฏัt๗บxไ๎ปๅ—‡˜ส,่ื‡๎$ฉ๕์#ฮพ|๊l‡}šR๖^ฺ5~‹vฮ€฿ี๘๏าคŸ*๐a,Šแ]”>E้ฏQฺ6๘@—”‹ีjQํ1aุ‰๑?:ฉ‡็C๎฿ˆ๐k๚w ฦ3b7ฌ_“เˆr5X…๎ฅHŒ$fหBW‡~฿ัํ†X ˜ด~Cls ่iDคภชb˜ ๆ!+ฆโsิo:ศ—'ลฃ<ข่ชƒz;R็1:‚ปฅŒtทmmขs๓๎?oŽาo™…„žm:ร)•$Q"*˜@ภ8ณBหฑั$S๚๙ฝ๕ฝ3‰C‰™ฦ_1 YWCq~!l[jฃNw๐ไส–‰v!”K๗๕ž•ข+LฺZธฒ^‡UeปR+บพI%ภDต~ซฟplh่Xมฟฏผ)j๖ัiฯมƒž้ฃ้฿ุ™!ุำ๖“Bฐ’[‹ฟkษY@N๑ข(aiรทVQJ=ฒฝWฏ`u™อB—cฯ žผ7s๑ežšๆหกž7ฟน'๘*\ู๙]j!ฒ๖ฒ5o\๒6Dtต1ธ-ฺื_f๑+๘$™“[ MFฐืฎblW~tAi\eWr•ใ ‚ล2VpSฎ•…QO`ศ`W~• ๏0r้mี2ื)d•|šSM|ฌบY—05u๛TG๗ˆ3 ^์]\์้;9นท๓ๅ6ยิญ3S.฿O/๏ณงwๆ xฦ–jnPญR฿ญF wงJ€AฏีTnท6—$† nJ{๙zฅ3#*.ํา้Kสถ`R$ฃภฤy๚Xพ"ญ:๐๗ฟ๊p๗๏ฟn>Š)ฑˆ2&๑๑ฤ๖๕ชv๖†Bฝญพั‰j๋kาu$์มƒ4:†ะ.4อugไB”7o๔ฒ ็inM๓๛''††ƒCพษืO๚a8wศ๛ูpใŸๆ๒ŠฉT~„_Š๔ธ{๔๖่u๗๐฿ยฒ™€;ฎˆ^V4 ฬzํ€6 ฆะถ? ณZๅ‘ํŸ•uUP*ฺฑ3ฒ„hฅ ˜ฉ+๐_ต}ซู[‰ปƒฟv”tŽ˜ง-l๘น)ฐzWZym๐?ย• Y‰ˆฺึฏMุ\ๆiกๆ๔วjโ(สฏฉ~ฌฝ~”ีฏึสJ/ฑ~R›ผฺŽูC~‘ศ‰“œHEข3ด}ฃดาย(ฟัx๏rฺภื(–Œ{ŽฬŽลฃษ2ำ›‡p๊ทWฺK4ฉOJ‘lg˜•9L๚ข"มOๆ „ยศชน\$ั›™๚๋(€๎อ x;~ ฮLธzฏง3แ ‡งตxlh.วขฦ7ใwบŒ_แ฿:<‰^ทHซPL๐4ว&,`mฐผ>ศ€e)(Bณ:Zuv๖ุBf*=]ิ๊ภœ0อZn๖ศโญ=ใด|zสl\ี๕ฒpž›•„8ศ„ชK<.อ๊vภ๓ า1ด’€>ฟHซๆ6^ฉOQพ„}”คvตณหd>W(๊YNๆ†๐)๗y๋„ัJ=แ๔๏_G์๊ฯil#RฐซPRลึ†ฏห๙1ภr}๎ฮS™้u—ะ|›hGภฺฺฌKฒใถ}ผŠk/N bํ6E Mด(2ืฏุๆDzะ_ณ—mงA‡ว•ม˜!ฬ…าˆไค๐k์rง๏ิq€๑5—ฃ]ว|แฦร`?`Ž/i๏๓E<#•LคjTบ{ˆเจูTTฝYทLษe`…7•๐2‹+XŒไฦ:ๅV0T(sฅ›ซEซœฆอๆ  "Qฤ*ตะ ฒ™ฝLฆYUU่Z_ฮฏ<์โ“‰3?ฌฆCN5ภBฝฉ้น้ฅะ]WF๗x`r ์๔S==rฐถ’0†ิท.ง/š๎œ๔Ÿ/๊น์ืฟชฦY ˜”;ปRƒดj‘หึIK ัlkRม‘NษษB3ฬ[’*V/?ษ X /ๆ฿YZ8๑%ซaงHjuS|9y20ิ— LMRxk(\ % 2พสnถ™* {vถžaŠ†Wแญ–3lYำV่๓bDดœ!“ƒ&?ยฃu ‘4ษฝษqŽณขต บกš"ฤKGz‹ล#L/๕–U1'^๚รK›ถO•มU<๏)t cA“‰ฒRฌฎ‹๙๋:/แ/พ4ๅYู6?+hv™„ณ-ฮ˜•๚" &Œ๔1ญคฑrด๏๒ฒa,_๎‹ย L๔ค`IลตUj 7€jxŸJน7ม_ฑ7‘ฎ๙)}ู0ๆปbŽXืผัx-H‚™จpภำ‡}:อEN%ฝืO–“<๘NM๑gฮ6ฌย" ซท@๕aeรจV•"!”vฏkkhSๅ\)Oฑบa่๚บa˜$อihT๖ำปะ eŠh โ จ็’OyV@JGwล7ด_lฌD๗džพpp~๑ฅ‚ไ†(?โˆš2ศ๒z€ไ„0‹‘l๎ฎKƒqฤ๏ฆ(€ึช—Jd‘มu๛> ›ผฌ;(:H๛eœLณ~ใaก/ษœ<๙€%ฬƒ^…ถพิ(yŸ๘๔’K๘}ง-00~ThษฆpSู}N`ญd์ปƒ๒นฯPญฯห็v—ด`ผ}.วŸภ)โJ“5ชUOk.ศq๑๎^,ํูS๒า˜ฎฃ็&ธwฒ0^[ผD3มญ ฃูYŽอ๙แe๚ะf-7–&๔Dี๗งe.อฤ‹ลฝ‹ลใ‹ฑc๏=บ—=p|๑Xn๒Ž\/9<3Njๅ๙<ิSฒdŽูe๘ๅ‡b/;ืbฟSฒํC’%|o‰๑qO น”(%จ9)เ ^d๙U /hƒ๐ฐorา—๒ML๚†ร™HYป! 0gญรPc!X๖—uู‘GFไ0Š`’0ทฐ? kจ^ u฿‰„ๅxปขJฤ฿ีตo_W๒9>ั#d พCNฺERP๎ูm๛<("ย4฿ฺฯปฬฯํ”KU'ฮt๋ฏZo”๊52R^ซ|“ศ^นHืฺNufJ‚oj'Gค%JŽŒุ็ภไ‡“ะย๓mp@ฌ8x`มิไŸ_8PhิCช3.‘พoDอ็วcŒ๖๊ศnธfPK)™‰U,)hJš"ํ•’ถ nE*GM๏ฏYฅใฟŸพkhOzมx?ๅซ$N ฃf–๏ฃ๑FjHื‡โžธช๋รวxูฮ่กจ๖!nD‘฿|Za‡ทŽaJ„7’/จฌขAาหฐ๗uณaš`Fฝต้ืšuฒk๚ทBaฺพ\(EMั๋พ^นLล๏PlŠฝวb›8‹ฏH’.=:๔1e zL0A0\u@hศM ปbK‘ชฎ(๚๊์‰ฎ^d'zปNฬ๋Oฒ%;O™๔)Mwa›=”;+N8Y '’Rล+ฦฬะถลXฅิจๆaาฅdaยฮกฑ\{๎`ะ3Mพ4ๆ—œๆa๕ฦ‘p้ฏP}ใuž่o6s3งฃUฺ@?ปขบข—ว‹ƒ๖jA๑์yผi„J*([Q%Z%อA่๚Gยล’๐œš๛H์œป๑ตฃn}h‚oฌถ?nถ”‹๗=7{>Cฺ1ศF,ดaติ๋ผป๊โโ๊โป฿ ›สdŽg2‹;ผศใ=ไ| kว{” BผวฌJ๑หกR)TฃฐีP‰cSภ)ซ๑}ฃŽืสฟc๎์ืป9โืWฆBTJจ€ึ{jŽห3ขนIใ๛.เ?“sอฃG7CปU๖หภยฒฑ<2ฺ`ี็aู.ฏ‰6;VํK-ฏ6›kศดซ\สkF# ๊ษ๋†‚๖ฑ–>๔ฆ'แ๘ีฃ#‘LWโ่ฑฃLืWฺŠ>53ร.“้ำญใ่ฎ\8dคsะื`gd%฿ฝ;ž ‘oจH฿4WปYพ๚ภๅฦฺ๏’]x} ึ้ฬE€ฮ/ุ๔ ํบ4b2›k-,อFํฤlŽZ๑Bn๏ขลฃlๅ้jึฅƒ๘…$ีวชP3*วฐ‡.ุํ๘ชqr_cm฿qส๕mJฬ๕ ์E6๚54ผืพ‘รoพ๔ฉช๏ฯ€X g„ˆ“n๒* Lฆ๙m~…ภคู[ิฦฦŸw9Šz>›‘>e๋m็x(ฌผ%ึIฬศ@ฐฤ๔ฏUKjAN–[eฒ-'T๕o™XœŽ๘ATง™€ฏ{ึนืmฝw๚`พ่ถฌร@ใฆ1เ๎ ๖๏ทภฮ#๙(š<G€DKAใจ ฝถQชภซ™ีo|๖l้ฬ‰lxวiตฯะศ‚:r๎ข XENœœ2ฮส ~‚พlFFาณi๖ก@˜˜–ภ@้…ƒเ‡ใรŸK‡#้tคำuม—็;\:;;/๔5Wนฟ•บM˜fL๊‰Pณ|8หqิา}๑C{Fห`Pqx<™๓บส‹๚ธ6ึำีนสMh2๔ภYorrb88'Z˜ฺ=pฬZม3ร0๕‡ฺVํvฃR\ลSแ;รฉ๎ˆฐzฏrcMJa%ืดไyqž†๋๖GDด็๒บ~นhเ๚ ;”๘ำ,๎Tœ‘ุ้‰ัฮŠฐ|พKษฟ‡ปˆํฺกช–Kš฿หรh$šiยฆš ‚๕Oeบฒ=๕$ซ๐R้๏G„ใ็ฺ(ฉ›เ‹แ๗)U้สwฤ:nฎWo›ŠฬRu dCถ๙ฃ!ubR?9Yฟฉ‚|LU'&๘ฆึฤB๑๚E์>งˆเ3(โฦ•Zฎิvภ์'J\jŸ™ ฐค๙ะ˜˜ ํ ŒWิะ๙m๒๘ู|oส)@โœว5x_Eฉnม‚bา;Sdฯž<œz฿้+๗tฟ๗ฝ๗\ฑ%๖๔H– ข žๅ?dาiๅ่ํ#๕‡S๏ไ?โ๔-Kาม{ฎœๆ™ˆ9tEฌj›ภคsนจยาmY•ผKทl+;7ิ_bืๅ—! k"hฉs4ว~g–Cžส („pH4 ๒ ้ฐ๐IPกะ9๐“Xห}ถœ F:S๙€[ ฅffเชˆE{}ƒ๑พพกHxฐท/”๊q:ำรแธา3w-"ึป๋ษaู๓”/ไอ(กตs]ƒ>—o@ํ๊9P๕๗vGฝ>๕-Œ.มว,‡YŠผ1M6M–k„๗ฦฬฏs#RฃชS+ั(|ฌตEE“H ปIi—Tœkฏ4ส ˜<่ฌ• ‚ฐtม[ผฒ Gา•+ถํA๒ึOทฌ‚dฒ†jƒ๘Ul฿vŒๆฃ|]ฑMคpิ๐€›]‹ฉœปB`ษm๖(qณq2oQ(Y,H{&“ภsฐFTใ๑BxEืม‡‰ ฦๅvฃจผ›Q‘์6‰—ฆุ…/ฐRฆัt๏แมLz่*•vฏeฏGฺ‚ฏฐ kฎu„ฯแlจๆ›Bm6ม…|+ๆ๗GZG‚A๗H็•e9}มjไฟ๗KG€ค“ฟnฃhšqƒ๒lR ฅŽE่>Jˆ่ร๔ฤ„3น5^UAA๏œ‰ฐqŸฦร€๛โฅWŠ}๗ผ3z';ฝ3ส!b๛ะวฃ—f#{ฬปฮ&ฮžMžKHB9"Fฉ@zฺสD๋X6ฆะ …ฒ ๖ –j๐งุ > ล% X(1e•ศ’ไ๙J5ข\œ!ImJjiตsc™$lู`T๛ใo|c๘า฿dแttชp๔๔ฅ)@l`ˆ4ิใอั 3N_ $;ฮณ_่๕6^†–ˆฺ'N6 >๕ะฉ#0š„š]่H:5>š\:/ถ —bP๋ำZWตฟซ๕์~ี<๊šใ๕v๛uฟzIhŒท4฿C’๑บรEดt(ษY้dห. r“ะP=9ยฎh{7อวชe_`7๗`og(%ๅ…5inะ''"๑X$ศ๛iฐ7>Gป"Cฃ8ะ`ใV ม฿ฬ้Aธ7ัำ“{ผ tWะ( าh}น เหxผฬSŸธ๊ VCฎg•ƒr-0&iทฐub$‰๏+งPšgตอŠฝ"yฌฒ๑ฦ7YiSDฃฌใ็วัธE[Uร\ิ†๖๏€พ‘C^OmfสญภอVa7ห -!ท@$˜ฮมลฒมV’ฯ›MVณไฬuk@.|2-l_‹r ฝcLข๙๕ษ*"ึ„ไข้–๏rSฝm žŠีเomKTH๓5 ๎ัึฌฝPมFั#๕๔#O8๗฿ฅัมฒฑรึภ ฅl0iงงZb๚ะ๕K”@„d์๐–xฟ]หฃy*eSหื/ฎ^ฝท{n๛โ*ีุ{ ฑ lณcศr’ฮŽั,‰ถdmํ{_๔ž#ลb~น๗!ุโtยn›=rQ*ห๙X์ะr>[W$!VดG ๓˜Ÿฐยถi†a M๖ฆ6ฐf4:ฌดแ5;„x3HOmhfSˆV_…œาiqYหH"ฉTย~bช†มŠฤu4๏vIฝซด^ถ~g2ฅฑ1ลโฏ›g ญ๙ค“!…Vๆ  )1ง0฿YˆดsH็›Ž$2t+ฝ๕&บ๎˜ุใ_<ฒ่฿c๔คbSฐ$NลRฝLโŠญวซ่Šฃฅ@ีั#ร‰ทš!›t:'c)œผ๗nถมZ Y๖?žlึต๔gŸGšIf)3๏Lo๏—Gฮฤ}ม[31oู๑rฏ?–ธนีธตฟ';3"D‚ข>@เužศ่‘VSA;๐ ๘๖ทYX*ย‰ัฒศLX๛ผˆงโเX ษ€œdjตže}0็#ษ!j-์Qdฏ๐œืKด$็2๋ถฎƒึ&1๚ค`yืุŽf5ำZ›๊W่ฏŒ€fฐ "'%žO(‰๏S…็# 5ำ๖ใส฿Ž)+uWฬ4฿[์ฎ“ใกๅ์$b๘‚3aOn็gมq๚6c๕กGT47c๓m]i{น]ดš'i%*เงี0!a5pฆBเ{€ฟงQ%ขแ -w๚“–‘#๚–œญ†^a๑%ฌช๋Šาจ1‰FดvUEŸห6—ตiฮLคษ#kkl™4CK—_%5+Bต์ถžH&YีTkekAณำ๕w๒ษiุXรtjหfpฏฏรƒEH|*1 ๗ั ]ู๋t่ำป๙ญ•ู?ยxิอฉZ†7ชHษvฤฑ‹๑kาc๑›Ÿy&๖ฬŸใyŒ~z๓ฏ={ถอ๓'ฮ=0MoใzCTพ•ซบฒษ๋&TF?S฿์rS+—E‰*%6Klฐ‹บ<pถฃ๎[ฑsึQzฝํย๏n!V.ทฤ8ฐ์hฆฒ๑[ะเ›อ๔)๗๔žฆ=F6›:๓gFiุฟ์๎v'ฮ+าุ฿฿WHรฝ`ฆจฎถคฺVcpณŽณ%šๅ0a/ฃ๙h"์?:gแฤ@29ง%0FE{7…๖ฒp—๕ฃ’`OO8q1)รqัๆwโRa๛‘\"๛ส…Mr`่F๒ณโิt† ฐV฿%Jมi.q_<๒๘ŽXโงsฝ๘M/ฅ๔ิปHบ@๘ “NKOรqฟ=2“โ๙้ละมู™ฬ๎ูƒs{๏œž฿˜ผœy๖UŠb‘ไvฆค`œห?8‹ ‰คทgw่4่ฬฬ๙๘Dv—Wwป์nฏ๎ฝia/]๛ๅืว๏ขฃ้๔ศ˜NฯŸ ฉ{ำlว9aฤ ซ“9iAฺ#Iv$~ท๚มm‡๐—๑*\ก oฒGๆก ๆๆ›a˜ C๐3  ŠคB็0œ‹H๐๋&…ัๅ๎xHฉ@\|ธ‡I๋ืŸ๓?xช๓XLยหMี จ%5yvGยZ8ฌคRX_p"ฒฆ ม†ปฃัdW2๚~McซTP›ฤ/บUn๎t!›"n๙b…‚ู$Bw _Xฟ:i‰๘๔ศiMPIbŽI‰ๅพ[vฃเf!6พw้HrŒ’ใœมŠ'"…:ศzโใถTrž;Œo[ฃทŒฒ*ษ๊ซFฝnฌ–Šœฆjb[š๐Œ&ท๕#ฉ1vG5 jXgา†TD ‹5ŒNลkฐฑฎโ๒2€๒-3เ{‹ธมํœศ‡ฝ๙[ณ๙›บ}kำ–ฺ5๔kaข์d{,@g8\มf#XVD๓ฯจ^# ๐Oท[มถฯฑฝญ9ถeŠจivฏ€มT5MFCrตญิ*EiRฺ5N*\ะn๚AMรฌ6ฤ๐ำค้มแฎ˜…ศŒ๕๕bS’ @šฅ rฯb ๔Nu|ฮR‰—€ธ YyZ๖?Jฮ๕‘Zถ`I Lบ˜9๙๙YPรฮ>~;ห_n๖ไ?ฮพ๗๎ป฿;๛ธฤšQ“zศNถ5ๅfูcทึ"ธฒซ!๖ŽwผฃŸWr4ใโL\ฺ่~Z๐Tึ- Qึบ™…ิูl]ne–b„ฃ;อฃ)คs๓hธ”ม€ิ“ลcึจpง4ฌ IP–"ฺBฟA“f8‡k๖[ฝํ˜วsฬ9ถ7ฑฏ3์tํtฮvŸํี›H์ vž>ๆt๓T๑ฺcxyฎŒ *ใ/Fฯ*ใ |:6>พ>:\X[ŽŒjpe0๖๓pmOอvกWŠZ"ˆศo‰ปม`%๙z์๋'Wu› Eฝ~ๅ$\kฌW*ขฺฯนยwฌ,Z๓ตEาด๑UŽ– bm๘••฿•‡โFใฯ.ำIบm[ฟญ!ฝาt2qmn•๗eจฤ”y‘*R\_g’PVƒสไUั+”E=‹๓ ืฑธo/}i‹ูฝXบพฝๅ=/_œy๚คnQB–Fr–™ชุ๊X—]๊‹‡ิ>ˆ์Š๗๒๙ํpvก0ถ๛๚z๑ฦ[˜o;ะ]‰๑:8$โ ทฒI„Nฮ4W˜4fŠˆฤเL'zโ:คพfe‚ร6+๙-„6qฐฟำ่2ำDึั๔eคY‘๑๕สžqŠLBM ="ฎ๖‡‘ตjฌษ‡้%ำ\็E้๕บฮอะ~0#S๐0๋(b_Zฆ]VH,Yเk/—ฒPlgแจภ) ^แ‡ &ขฬฃ›ฎฆ "๙.zi๒Š๗ฦ–zN/,ŒFย๏hฝส็‰ยz*ถŽŒ.ภืฝCดั+ว๑๋ˆœEˆ๔฿%MตคH ถ^๋| D;น`ƒh#‘ ็•‰%(8}iโŠmธAW˜XNฃSm`๊จwย๔/สงiถช}(์€ุ!่Š›;L๕ “ธ>ท ตƒส„?ึpHPๆA้ัW qyฅ,a‘ฬžf …9˜n๒x€j68…แฏจไ“?Aฆ–‹ถSLฮXภชN‡โtEœฝ.ๆqy;#n‡โpชŒแwฤ็ก/\=อ/Nธ๑๗ฌสz–ižp๘ฎดัๅ”‡\ฎ!—งหแNปœ~ๆuษฌพr9บผฮA—kะััๅ€SgฟSvxืn?เํaีฟw4ั…6l้ใ๖^ฟMFซ,๘S›@o•ทบSฏ›&pNj6ˆNร‰่฿0P” ศ&7ห ำ—๓หฏฺย<๒Ž&>AmลX)ทVยšnป>ษ็ ๙บ@ Lช™F;:ว๔mBQซ๓ํ ทMเ„<œใ์FYv>ลe *c๓)ฯ>v xGX‘FญrKใ7ุโEัฏๆ๖ZNท’ฺขdลตmT›EฮAvม{| ็2ข*ิ€F๛)ภ*++ล•…ฯ๙Žล๏j-ฆฌGl1ช ้yZH\ูƒuy๊๐>‡bเ} ว5๖รป/`t‰ ปcG—€::~nภHไฮ6‰‘Uฤnทkฅ๏าŒศ(–/ํŠฐ‚6๋3;ทn๒๎$ฤkPF%%๗ๅ‡ีQ/‚`|๑ม!ผธJscจ•˜Ž=รษm พืkญHu/”0jก๕ชกๆšฯด‰ํX‚d&ด้lMชQN^ณUฤ’ไnส|D %คคลศ๒\’ฉภ๖ฐ[H4ห!_-Xฦฌ๚†‰”แ๕ฉ‰7เฺฤี˜<–ฉ)ฐbOปบ‚เ๊ไช›{ย็ปบฮ‡๗˜ฦว !vD,„%hิ>ุ เšนง9>8ูู๒ฅ‘bื76ภC 2๛ƒไ๏ฟ฿฿๗lHห˜€'zโ}”gŠJlgถตขOุZO‰œึำเŸ‘๕ี ูญป:๒Jฏืแ-ม๐็;\บ[ž`l็ฏ^วwฆ'd‹๖,tt,xปง|ขร’ๅพฌิ าQ้ะsY ๆœpX‘-@DrHUฺ wกB!)ิžข€ง๐ƒiภK,แvลœžtทฯใp๏๑0O_Gฎธ ถ๓W™– +้"tธp๒ถ„‰ฝชGnป-๙๐ร์…^ˆ^บ๔.อ๚{ืฅKั^เtลฐ…H=(R?PRซ๏Vธ5Ÿษ๓I!#|๙‰/?ฏรซืAง3Œุ๑ฬ^o~๊<†ฮัุฦ๓wเJวแ_้Aœถ‘Ÿ?ฟ„ ๘’ไะ]Bา0ูX†7ืยv*๔ฒจdตuจ0ผ…๎ศZwoบ•อ€Oobฌจ*ผ`X;ESำU๘J 5ฎtJBพxฑ—G๙'hNo็ปŽฦบฆM๔^ผ('บุ>๋&้๋๕‘๑ษช‚F฿Tˆ˜mลจj!๘ืcอP,ีนกมŸa˜&mศ3H’lทISว•rา"ฬคmถ์ําู=ฏTชว๚["~j/ฅŸ"รษื}๙๙็ฟ่Jตฟˆ0แฎ5ฒC~šŽเฮŸว๛?I_|“~๔4ฅ’gM‚‘=ธ“`%‹š45ีNao่z1ฯ๒๙ผฉ—ถ!ต๓๓ภ6ม฿ๆ9ฅ'ขพ~ธoY22ฑ฿ฺนŽ]Gq๖ส๖Œว ึLY”$ŽิึยJขnn๊oฯ–†yไา'b{ diBำPzษ>9w=ƒ.ฬล2ว`๘X๚\ฝฐgฬ‹’ย>ํ("๕=%‡็ศฮห๓จ)ะlG๗’๎:[xq!vธcฉ๑Xb|‘–ย31qH‡c ๐ฦแ๋๘'mป‡k๖{คฐ%7ฎ>ฤ๛’L–Gรgอ`9zJง๕|๓œtฅ7€ืXะ’๓ห#o=8CฯMR†ชฮ๔๙ณ#Gผg็ง––ฆ@ปภeD2˜4พฃ qนI„,… 4คšฉ9จj/พจ”4“ภฐ+AZ.–!ืฦ‚กฐ’ซ‰คษ๓ไ2ง<ๅฺคผ›นบ•ค}I—/๕2*"๚Vร †kิXqฯร`ฉˆEqZZวิภโ6J้่แ5—[,4๘&”๗U^แีŠi!eษžbจฃฝ\žภ็l2๗ุ็ฦ!ks๒7ํดm+TผีhjัXY3ฑื่‘ฦG ‡&bLึIผk๊ aฐL.จcUแ;‰ ่jธฟ&ม&€"ศษ๐‚ฬ๕เทkค„“ํใ:rŠสไ๒ิ$<ฎ>กะAยใีณJ 5ห๏Q;”gเ9™s็bqC“๎๗ช๒aฟcฬ๋ฝแys โส[FBดฉ&ด‚ถs>เูีZ๙7ดำy^b๖ œq“˜ภN%[ทV็็ฉาผสู-%zวLG๐4&๏ฏ2=ะBOว~นณgN๖uD‡‚EJ7}ฑ“^–"˜Xุ๒IฎŸฅU่=5ฬ–~Dฑ&พ"[9ปั\ฆศ=ำ๕0ลA“พž8ำหลํะฤEด๎i%๕ฃา%“!ีvฏ"#7ฤ๊Pี$๔aฺMใแDVov๕U’HkฮG๙ฆด–ๆ&้—๗4iฟ‚}q^ผส>ด„“า%‹yhpค๘แXJู;น}ตgแ€ฤ„{~าฦ' ๛& ์Z๐$ฃ1ƒ”?บwe‘{๎9!_7a๒uJ)ฒ่/Rด๚ษฤxขร&Hˆšพt Hb็งB+๚ภ=๚ส ๅุFtep2๔+ฺ•} ’ouMBGวฦฌฦสใ๙ัxnŸ3ล้EF-ม-yผฒq†…g^_xฬ,พ{ฦlq*_”<ดV$แŸมXcWsŠ9-oรTฑข9๓๎ข๛x๑๕ฬlJŠพ"ฆ๙๗rป๕€ŠL'F3โžfํgฯพ‘;v0ฉd`๐7›D[ุัุใ-m็ฏผQม”•…ใZห&น.#“ฦฺdต&ฝ `โAณ$์ซ5ล0uƒห8…]ะ#‰{B.FลIห}sตจฌ3sccCcFฃถM)Œ—@ฌXฦ๐๊J™UฺKฑนดฆMG๛๓:a๔๋(Lff๋a|W†Nœ % ๐ภฝeไถf!ขŒZซเโ`ฏJ๚==ร๗biB&ํ|kTJ๘ว*ลzฑธZ*•‹ึๅiถ็ษ“|ๅyๆ‰-eBCmภšห4M๘Žhฟถh†ฤ\จญa_฿ุ0ห 56ˆ^,Y\‰ฟl>S‘มฝฬบีz๏tW/ทกgmlcฎํ<ีvฮ™ ๆื11๐tƒŽ0ad ้ˆาํ๙œตย6ุ‹6ึอ3ดJฎ7๓Ei|๕?nš˜0ƒจัw‘ฟ๏\8v์l’ลๅ]ตhฑ4ฮำฒทœ^ ูVŒtปtFV™ŒJ~ฏX(๗{_ฮHๆพ}ใ๊@า๋‰dŠฒ'ข๎|>{ส |๚7‚ษ์ไฉทฬ,y๗!ิœ+7 Ž๏๓%™ˆว๛กhใQ๗\O…cKใพ๑‚wฒ๗ยฑEm)฿๗y‘ศ้ nถฃเพฑY™CMn1ฆ(‹ดgบiใฬ`•๛รq* MfI›=D–Uำ–ฆh"ขฒชก›บ%ย8k=ดœyธnj๐วŸTื๕ึ’Œ@qœา…–ตsYziฝUB‰dฬš-ญmหค%Kp;๒|v๕UV^—=ะฝ“‰@์YN\8›?ปk๗™aO}5Q๎๓$q/Yม‹ปฮžูห’๎ศŽ๑ๅ 5VMส00้‡)“ BฅฎkMษ aญๅฯž๋DfRt๗๎(lGท \YŒF#ัำ๛.€=czธe{,K‰# R?>'สพ—ถ5\hCำ?=2khม JZ๏f“Ss6ฅชษPึ!๕ขj๎HrF้ฮ+…ญŒถอ+…ำึ๎‰RŒ„aฮณฃฎIบขร็ถmt~Lวo๔๒vช?ก<๎ญOณ~hวr€*๎ูฎC)m›=อ†@ฟ0๒'™ฒอ‹šc–k>ฌวTš็pžณWtฝั๕ฤๅ•Dโ lลDBO€ถUQ๒nฅ; › _YมญK7QŒ+X$p]K˜8x[ฤ™ง9๕Oฅฐ2 ฺจ[5€่`%c}ƒ”KpjTQHŽ<yพย๊–Œ”Š๙ธ>ก์๑dุ้๕ณO วƒซc‹uw;]ก๎ฦ‡ู•ฦ๊๏จ‰ฮ7zgฒซsฉ>็A๑Yศ๏ุฯ๒$Œ˜๋ศ ้‰ห~‚}†fข~h๛ยvณ\ๅ๛-U๔Aw!กห:tวกCoรR้bX< Wเร:๑ฺS \้‹6ฯ†ฉญž ๊0tฝz“ทQnRsผF๔v๘ฦ่ho0Rbฃ0!e’%ใt;:7น=Dw{v๛ปรiอ3ฆFส ์้ttB๛,A=~ ๊1/๎‘ž’‹ฃ เยr๊'หƒฺ ลษŠุ์cf˜ฎซะฝณ„86Kวฉaฬ@ฺ`฿ฬkxำํN่n”–ŸแmJIS8TOฦz^[rN&'3๓Y€ิฝล˜<๐๕8ƒฮพ^ๅ๑๛ฒ๓0#ิ:Žฝ…1GOGst๙zF#นฎฮ].gดO=z๗ัะ๎ณปีใu>/{\‰ู้วfgโžŽNwtb๒ษ‰A'wฑล=๛็=๎G๗ฒK๗%'๗#›`ภฟง4• งFำžŽ‰c๓wŽŒŒDpj_์b>๏ค็Nw—รHํ๎๚Qg46:7u่ะTgg:„‹’ฝNๆ่๔LD"nOGVUู€:๊บฤb1งๅJ$\}v4Œ?bยบภฟฒ& ƒ์c{[ุ.๎a“{?๘žวถ‹{฿ุัลจั„ฟศ1๔ธญณ[žŸ 4‡,; ‹่d ขGฤE“†ๆMPซ[Htฯฬใ๙ฅวgpฬฮŒ฿ธิ๘ฒขภ๑๖k้6 !ูถ_ซFซร’eภF็$`qM๋ภาฃ-เฺรAงrd‡ ™‘ฃ†ฅXตq๑ฐiฝk๛๓มน|0ฟ_ฉกˆิd J่*ค†ฉ•‰—ฅจๅฅlv)c€G„C&Kๆ†yๅ 0บ5ธ œK๏‘$๖)xโAย๖Eแš‡›ม›S›ฮžjsศeฐ๔ฑ~฿๛ปQ๚ีษœ์8่+ ้าa‰ิภd๘๓AืCพ^š๕~  ฆcvpˆฎั|๔4ชฺ…7ิhž#…ใ[Q{hH๒h‘ศ†…>๐fฃป์ ะใู๑ฅ้t` Q’๓Zˆษ,ฬะVe„ สอ4ธ‚ฯเ8o‘ฬภืป™ำฯjcวฏ5โCŽ_๎เ;—พ“พ_ฬƒ3ฃƒs$แวภTŒS้lขZ_?v!>ƒตmQP!x–โฦijษM‡ Xƒฟ+ต๛—q”t„Z–+›…T0ฐiส็˜2}\6ผฏ?mสวงOฟ{u๖ฦg}ฬ๒ฑ้ำ๗หWงษ๛แ๋ทzK”ผ๛ˆฆ#@ ร.`›ฃ๖jิ7x๊~/น๊`๎_๑&nx@พš๐~X~เ†QyxtX0ฯปKะ่ะlั†N4%๑ๆ"ง/lแ˜่อ:zƒu5ห@zตฺ๊k ั@ƒ:*อ? ZŒ~๔=าฑPKWB!รถา.[˜Š4ีฒา&H‹Vฌชฅœฒรๆ๐๖$ฑ฿?พgœ ขŽ6˜ผ6ถฐtqฏg1ๅ•XZš๘{ไํโฦz@…ƒv๎ั'๖๎๖/ฆง๗^\า5๎M;หช์w%ั ษ9ฤ€/4ห-˜Lส\ผ˜๙G๎ปU/dnฟ-sl๖ล-}œxfhIฌ!Jห๐ฎcฒ'„kฺ•ั  ?05เฺFK_hถ˜h{ฌ={“ฃีํ๔ฑงู••ฯ บฯษู.gะ๊Lv๖\NO—ฯํt๚;†d]๘VJ?!๒€0už>ู๏าผฌณ“yต ึ๊ํ yŒ9Ÿะf[ ศ‚œอว„ูกg"ซซใ๛&N,4B๘ำ(-ฎฎbœฦ—ฌซ4jšN_%œŽHN)‘ฌดŸ4รqๆ)โ&’ดdม%ฌๆlๅฎSซ3|ว‡ไ๎ศBฌ/ภCม“มกw'ฮJ~e^_N’๘›dแ@ฒo0๘d$xrฦ฿nV&ญ|ใŸๆO$ `x&`&ถฤiภZ๛L„ญ%†n>cฟืyา™: žNฯx„—๎` คg๓ฮ>๚’;f'=nœsž‚รq–ลNฏŸ~0ฯุ2^„๔ณญ^ีฬภ2 – œบkมบN๔Rโ๘“ใz้ฉq]_#’…K_ุออtW'๖Œ3}ฑ ใ๚S………ยว.-์iCv!หŠ”’i๊ฮ+ ๎w๚้มšVฤF6ธ‰bkr!c6หnsQ฿vฯ6rฉ?l”ษฒ็VXิ0KR`‚ฝะ๘jj6\xœs ไYฏผ‚+๛+_ภใ/<)gท ๕ย ฐ`~8ภO‡ €‚*คก3r0มœXบ+ธผŸฑหมป–Nฬƒn%{ ษ็Ž็ž์฿y83slAY}แุLfxlX๏้้ั‡วŒืฒL฿/‘zร1ข6ี2๐f`C‚๔5ะ_Sqฌcเชpขร’๗e[ท-žอ6ธ** ~)<ฆํŒ๏๎๐๋`ษtฅฃืื็pewทฏgมqำ”3Tb1%ะํˆ8ยษ‘\n$=๕+ุช น™" •}(สŽ์๊Xิ}?๐๗ yฝj฿TW๏‘.WาŒk๑nfj๎Xn๚G@$!ฅ  Bฐ#สRb฿b฿"$ยŸhข๙emณ9—Eรw ‰๏ @qผ–?ซฑ8™ฅๆ๚l€ฑ“ัฝ€sา๛ฺงLๅฆ)๏ชqห]”OE๗‚๏mจ{ˆlT๎บ=*ขFจd๛3ห]X#น/ Uxวm๎}ไปัโpp2>>@ๆi_dหีฯกืฬ`qxT_O ฅ6CึP xเฑไGMพ€ด‚6)”ฌสฑqœL†–๐gัEqภ_นƒส็ชoำแ๏๖ ั'jE่<$Y ณŠฎ่coฉ้(@๕4ŽDŸ(ฒU\๋ไžI/‰0!3a•ฤศขทp|–Oฯ&'bึ็œˆ้๓หึ“ฑษษ/ุ่>‚TงY^…j+p‡ร| Ž“หC‚4 F!ฐ๚อ‡cw้wวk‡ใw๋wล฿~)%^€mโ฿s?•ใณ\+*dV~„ญœVํH<%มดQ.)ฃœบx#~L‹L7็a‰ฝ ›ŽฦcฅWษ๒ัR้y๋ฤวธ่โV‰5ƒw^‡ฤLq็น็<8ฏ ๔o™ำ(รใ>\ื์ถ•ƒW†ฃ‰phฆํQŽ๖$๙•ไ@๓ {๓ก  *!mฆ,๕็“m็อ™7น_ƒ}>๓Vฆเมธุอซp„ญ๐์;–ฆ฿<๓ฤฬŒGIฬ๎ทน๓ฬยGโฯลูกู„โ™ฏ<ฝ๔๘๊gเ;ฏgแ็โฯว$AbŒq}ณLœxƒฆ\aฯ ๆt(!งษคdb„น",ึfฉยํ๐A+ฅiฃ %ๆฐร%ฟฆ—kฒ=C Vฐ…Vd๛Mgฤฝp}‹อY!ศ๖Bส?Lเฉ บ๖ JoR~…Mnถ(ฌาD•ž“$Žiฮ@s|ฆ๑คIwธ9u็Bi`SHว•ฯœMทขx>ม‘6๓kพ9c.y๏ฉnๆ"&vง6Ÿฒฒ+ก†^๗ฆCjาๅpธ’ชh๘๎่ด๑๋-t ก ี~๎†=S ’ง๖ุหiyj฿พ)9=เ๕yแ$30ฝ4MgQ๘•!ฉพณnTšุ= <_U=อฉ=๖ˆม,ZฟณiไY๎เฦิ\ฎ1ภแษNLฒB๕่ีข‰ๆฐšŒ๋้h4ญววCท๋๚ฎผ๛ˆ0!oia•ศ4ำูˆ๎šุcฑX|"‹ŸโษXlำูWเˆฎฤเ ฎ‰gค๙bB…9ขฬfูน-ฑดฯ:ื๛ฬฆฬTฎฌ#„o{xุ@ื:_‹x๋๕๒ธ\ŠU ็ทใ~ฑฒ9นdฤ†ฮอฑูชc ข6๚าฬlU ไdซ๏๛ร|ลFทํ2“ ฅ\๘ี_ed5ฬ—kA๗X#หค3า$Iฒ„ฌ…,…}WUI_qvw[ขิT๎ใฒี\๛^iปsปˆ๗3ธห๗๛บ]พรJ"?67๐œวูํ๋๊cฮnๆW(ฝปKh฿‹วโพํฺดฺีำฅช\€=ใฌjั๋t%๒g]ฬ๋ํ๎๖ฎ๔ศrฝ้|วทvFคVสs‘?๔l้“IฮQทo~Hำ†!ต/่ท=ฑtbfคOMdษจ๑H„•ฃ•zๅว๖ข๏ึJŸ?’Žีณ=ปGฦวGr ี๛2p‰๕hฃฝbp_ะ2ี2ฤํ7QบสšuJ‰U\Aฺ…”V5ฑiJqช•ดky›U?ตoHณซˆถ:@ ฅƒช]ษ|ฯ๎ิ๔๔ศ,TFŠบbดš…<2šB„ณศ{€ฒJฦ ™ RB๓<ุ๘๖กKษไฅCฏ;้+สษืฑœ:<ฌพ๎เม๛ไrnzwิใvzฃ๏–XSข|#พcฒTฤpSYEDุใ์/e5D<฿็ษC**d ฐม”•…ม$ ๔5ฟQู5ฦยGฏ์่p๔จS~Wะแv†]ŠK}&ๅux‚N6s‘'ไ๊VTึ?=ๅํ๔สŽŽๆ๐9\Ccฒห)ป™ƒ-1'K8œ!—+ๆพ๔ฌหyฦงำ๕ฬCnวฝู=nวยู๗u(l8ิtปบไมๆูOpผฝ^ทใ_]ฑ€xc`d```dบ0ez๖ฒx~›ฏ œL p้โS`๚>ใV†˜˜X€\ฐ4w Nxc`d``b8 Aฬ๗ xญ”5’1E฿nbfซส‰™™™šR™™m`Se>มคNM๐I๖<†ฎWmฮ<@un–4 ซf๙ฏื^๖Rจ์ฅRi๑ฯ^งวX€ย^Fด>แฟ-Z7Cว่LT๙ทPh$(-ณ฿}ฒย3@cŽJง์๑n๑ํF้2qกะ)ŽAั๋‚€ณ•’)Xy>ตุœฃPำ&=ิJˆ)ัUz…ุK‹ฌมฐ`3๗šœGƒ}!‹ยโ9๗๓€๚L1ำฆล`Sินฉ\ึˆศชKe.็^iโFdr5์Žซ๑เีUยz‚:Tg๐‰ฯmhี๑สu‡™ฺจUฦ†-5๊r7ฃ‘ฺZ1sฒR~‰oก uHปๆ๓ž๒'๔#ื์a๓›ฬ๖ฒฃ{bฤXูf8่์ ]ช๓jไ๚ึguจ_)ฌ#๗p‹สvข 5/โš52vด[xตภศs y}ชส‡๊ๆ3~ษ แซšี๚Œฦฯ‘N๘ซmw‹u9๋ๅ˜}g0๔hฬsท฿ะ่ๆ9#f)r_ ย7”s฿vืQMฒสฉา Qœm ‹y๒คฃ5๙s%๊k_„‘3ิ฿ฮ‰ญŽไ Dƒ\หต-2ป/นฬขไๆQ้ฟC_k™๕Mญฌ™YCฤ Z…%ล9ซ๊~-…UGŠ*ฆงย{9–kบ’=ษ่ลZท|„ž{žฝษpœ ฝo{ึ๗ˆ๑ฬฌต‡`๛”Uท~๎ฬ.†}Q'3J๐i!ะ1xมใ@ภฟqRoฃ>ถmถmถํงณmถ=๘LHAfศ …ก4T†ฺะZCg่ ƒa4L†ูฐVรfุ ๛เœƒkpžม;๘†$ ŒDdขสŒrฃจ?šŽNขD~bฑŠ8O<$>‘2ูŽBฃT)jตžzHktmz ฝ•>L?`์LˆษภTbz0๋™ซฌ‹-ฯถeงฐ[ูซ์s๖—+สuใ๖qoxŸ‹ฏภ7เ็๑๙wBzกฎ0B๘d[e์ฝ“GœagS็็6็!็3—๊*๋๊่Zํบโึ…CหGo<ุ“ษำภ3ุsS8;.…›โx^/โื^ล[ปยง๘ ๛ฺ๙†๛๖๘~๚ณ๛๋๛ว๚—๛๚_BA*X*8(ธ4x(๘4d ๅ 5 อ ํ} ๗ -ฑž8R"พ“X)ƒTDj+‘ถIOไ \Pฎ-’ฏ)>%‹RMiฏ Vฆ(ŸU]ญช๖R็ซปีร๊- 4Ÿ–Yซฆ ิ๋”.๋น๕๊๚P}‡ิPŒฦpc‹๑ุ”ฬยfsฅyาbiVซตภฺo}Š‘ฺ‘‘‘ปQ:š-ฺ9บ8z7ฆฤสวfฦ๎ลฝ๑\๑โ๑บ๑๑G *Q&19๑.™#ู(9?น&y ๙8๙=ๅKYฉฮฉiฉ?มดUฐl๖ำตm+ถmถmถmw˜];ZใwŒŒŽ—‹IคOTNŒO‹jYlนfอcmouYGZ๗[?ุุFS{ปล.ุ7ู?9ช:†8F;v;ž8ฮGฎ’.ทkฉ๋ฒ;ฏ{ฐ;์ž๋็ๆ้เ‰{ฦ{{ ^สปม๛รืหง๙N๚ณ๛›๙M~ั/P+ l| ึ ‚3‚{ƒCYBMCรCณBWBรeรzx{๘Iค}D‰|ˆ–ŒฺขDtS๔SฌbฌCฬ{/๏Oว—ฦ'๊' %q0q9๑/Y;iH ษmษ[ษทฉก)%ต!๕(๕9]'mI ้ณ้™!™i™+@EภDฬึ—€วภ0Xl vƒเ(p1x•ƒZACก DAกeะ^่ \c๐๘$R้†pศVไ1š ํŽr่),+ึ›‚]ฦ โญqฎโk๐DยFจฤa27ู…๔“'ศTEช#•คfQง้b๔zฝ~ร”dZ2 3“นสV`‡ณู์u๖W˜kห๙ธ1|๎?”ื๙ณBqaฐ0Jุ"<k‰>qฆx^*) ”VHGคwruนซœWสGไฟJ%ฃฌP^ชญิด:Uฉ^SiฅดึšS;ฅฝึ‹้}uB_<@Yฬถmvูถm^๖77ถmบu๋็m5ทู๖|{ฟํ‹ทปฏํ(ฑc๘bว9 ะ˜ Pภ.เ"๐์N)ะ‚฿ก2Psh4ด pธ"ž ซ๐1$;าXศY43ฺลัป่,/ึ›iุE์)žฏˆลใ~Ed#jˆ$qŸ,M"I๒๙‡ชC ฆึQa๊%]”DรL6ฆ13‘a˜ณฬkถ8„สnaร์-ฎทœรธƒKพ฿†็…bBWaฑเฏล’b1.•ฺK„t^ฮ'w‘ืษ๛ไJ%eB+W”๏jiตฝ:\…UG=ค>ำŠkm4Iปฏ็ื๊kuOฟcไ1aพqมxnๆ0ซ›อูๆS5O™ฏฌbV[kˆ5 [ทํ6`๏ถ;5œ!ฮtGw๎;–๎dWuฯธ_ผ ^{oฆy๏A Zเuฐ`ฐ{pA๐H([จwhjH ] g w แ‘–‘a(r'Z6:1JEฃั๗ฑZ1%๖"^%>4พ#~6Q,ั/ฑ!q Y, %/งฒงคฅŽฆ~ฆ๔yฟŠ?ืvๆฯM00_อฐ@.x|Ž5RaF๎๔8-๎Zแ๎ ๎๎zฮ˜sคฮ›ษZ•ตฯw~ „ ศ+,Ž เyิซฒ<Ÿj^ภ/LtŠจ%๐bZI3ษ#O|๓ย5—\๑F3ฝtำร€lึ๔QŽsฺี๓ CakePHP CakePHP Rapid Development Framework pear.cakephp.org CakePHP is an application development framework for PHP 5.2+ MIT License https://github.com/cakephp/cakephp/blob/master/README script php php php php php php php php ZoneMinder-1.32.2/web/api/lib/0000755000000000000000000000000013365153155014452 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/0000755000000000000000000000000013365153155015315 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Log/0000755000000000000000000000000013365153155016036 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Log/CakeLog.php0000644000000000000000000003660013365153155020061 0ustar rootroot 'File')); * ``` * * See the documentation on CakeLog::config() for more detail. * * ### Writing to the log * * You write to the logs using CakeLog::write(). See its documentation for more * information. * * ### Logging Levels * * By default CakeLog supports all the log levels defined in * RFC 5424. When logging messages you can either use the named methods, * or the correct constants with `write()`: * * ``` * CakeLog::error('Something horrible happened'); * CakeLog::write(LOG_ERR, 'Something horrible happened'); * ``` * * If you require custom logging levels, you can use CakeLog::levels() to * append additional logging levels. * * ### Logging scopes * * When logging messages and configuring log adapters, you can specify * 'scopes' that the logger will handle. You can think of scopes as subsystems * in your application that may require different logging setups. For * example in an e-commerce application you may want to handle logged errors * in the cart and ordering subsystems differently than the rest of the * application. By using scopes you can control logging for each part * of your application and still keep standard log levels. * * See CakeLog::config() and CakeLog::write() for more information * on scopes * * @package Cake.Log * @link https://book.cakephp.org/2.0/en/core-libraries/logging.html#logging */ class CakeLog { /** * LogEngineCollection class * * @var LogEngineCollection */ protected static $_Collection; /** * Default log levels as detailed in RFC 5424 * http://tools.ietf.org/html/rfc5424 * * Windows has fewer levels, thus notice, info and debug are the same. * https://bugs.php.net/bug.php?id=18090 * * @var array */ protected static $_defaultLevels = array( 'emergency' => LOG_EMERG, 'alert' => LOG_ALERT, 'critical' => LOG_CRIT, 'error' => LOG_ERR, 'warning' => LOG_WARNING, 'notice' => LOG_NOTICE, 'info' => LOG_INFO, 'debug' => LOG_DEBUG, ); /** * Active log levels for this instance. * * @var array */ protected static $_levels; /** * Mapped log levels * * @var array */ protected static $_levelMap; /** * initialize ObjectCollection * * @return void */ protected static function _init() { static::$_levels = static::defaultLevels(); static::$_Collection = new LogEngineCollection(); } /** * Configure and add a new logging stream to CakeLog * You can use add loggers from app/Log/Engine use app.loggername, or any * plugin/Log/Engine using plugin.loggername. * * ### Usage: * * ``` * CakeLog::config('second_file', array( * 'engine' => 'File', * 'path' => '/var/logs/my_app/' * )); * ``` * * Will configure a FileLog instance to use the specified path. * All options that are not `engine` are passed onto the logging adapter, * and handled there. Any class can be configured as a logging * adapter as long as it implements the methods in CakeLogInterface. * * ### Logging levels * * When configuring loggers, you can set which levels a logger will handle. * This allows you to disable debug messages in production for example: * * ``` * CakeLog::config('default', array( * 'engine' => 'File', * 'path' => LOGS, * 'levels' => array('error', 'critical', 'alert', 'emergency') * )); * ``` * * The above logger would only log error messages or higher. Any * other log messages would be discarded. * * ### Logging scopes * * When configuring loggers you can define the active scopes the logger * is for. If defined only the listed scopes will be handled by the * logger. If you don't define any scopes an adapter will catch * all scopes that match the handled levels. * * ``` * CakeLog::config('payments', array( * 'engine' => 'File', * 'types' => array('info', 'error', 'warning'), * 'scopes' => array('payment', 'order') * )); * ``` * * The above logger will only capture log entries made in the * `payment` and `order` scopes. All other scopes including the * undefined scope will be ignored. Its important to remember that * when using scopes you must also define the `types` of log messages * that a logger will handle. Failing to do so will result in the logger * catching all log messages even if the scope is incorrect. * * @param string $key The keyname for this logger, used to remove the * logger later. * @param array $config Array of configuration information for the logger * @return bool success of configuration. * @throws CakeLogException * @link https://book.cakephp.org/2.0/en/core-libraries/logging.html#creating-and-configuring-log-streams */ public static function config($key, $config) { if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $key)) { throw new CakeLogException(__d('cake_dev', 'Invalid key name')); } if (empty($config['engine'])) { throw new CakeLogException(__d('cake_dev', 'Missing logger class name')); } if (empty(static::$_Collection)) { static::_init(); } static::$_Collection->load($key, $config); return true; } /** * Returns the keynames of the currently active streams * * @return array Array of configured log streams. */ public static function configured() { if (empty(static::$_Collection)) { static::_init(); } return static::$_Collection->loaded(); } /** * Gets/sets log levels * * Call this method without arguments, eg: `CakeLog::levels()` to obtain current * level configuration. * * To append additional level 'user0' and 'user1' to to default log levels: * * ``` * CakeLog::levels(array('user0, 'user1')); * // or * CakeLog::levels(array('user0, 'user1'), true); * ``` * * will result in: * * ``` * array( * 0 => 'emergency', * 1 => 'alert', * ... * 8 => 'user0', * 9 => 'user1', * ); * ``` * * To set/replace existing configuration, pass an array with the second argument * set to false. * * ``` * CakeLog::levels(array('user0, 'user1'), false); * ``` * * will result in: * * ``` * array( * 0 => 'user0', * 1 => 'user1', * ); * ``` * * @param array $levels array * @param bool $append true to append, false to replace * @return array Active log levels */ public static function levels($levels = array(), $append = true) { if (empty(static::$_Collection)) { static::_init(); } if (empty($levels)) { return static::$_levels; } $levels = array_values($levels); if ($append) { static::$_levels = array_merge(static::$_levels, $levels); } else { static::$_levels = $levels; } static::$_levelMap = array_flip(static::$_levels); return static::$_levels; } /** * Reset log levels to the original value * * @return array Default log levels */ public static function defaultLevels() { static::$_levelMap = static::$_defaultLevels; static::$_levels = array_flip(static::$_levelMap); return static::$_levels; } /** * Removes a stream from the active streams. Once a stream has been removed * it will no longer have messages sent to it. * * @param string $streamName Key name of a configured stream to remove. * @return void */ public static function drop($streamName) { if (empty(static::$_Collection)) { static::_init(); } static::$_Collection->unload($streamName); } /** * Checks whether $streamName is enabled * * @param string $streamName to check * @return bool * @throws CakeLogException */ public static function enabled($streamName) { if (empty(static::$_Collection)) { static::_init(); } if (!isset(static::$_Collection->{$streamName})) { throw new CakeLogException(__d('cake_dev', 'Stream %s not found', $streamName)); } return static::$_Collection->enabled($streamName); } /** * Enable stream. Streams that were previously disabled * can be re-enabled with this method. * * @param string $streamName to enable * @return void * @throws CakeLogException */ public static function enable($streamName) { if (empty(static::$_Collection)) { static::_init(); } if (!isset(static::$_Collection->{$streamName})) { throw new CakeLogException(__d('cake_dev', 'Stream %s not found', $streamName)); } static::$_Collection->enable($streamName); } /** * Disable stream. Disabling a stream will * prevent that log stream from receiving any messages until * its re-enabled. * * @param string $streamName to disable * @return void * @throws CakeLogException */ public static function disable($streamName) { if (empty(static::$_Collection)) { static::_init(); } if (!isset(static::$_Collection->{$streamName})) { throw new CakeLogException(__d('cake_dev', 'Stream %s not found', $streamName)); } static::$_Collection->disable($streamName); } /** * Gets the logging engine from the active streams. * * @param string $streamName Key name of a configured stream to get. * @return mixed instance of BaseLog or false if not found * @see BaseLog */ public static function stream($streamName) { if (empty(static::$_Collection)) { static::_init(); } if (!empty(static::$_Collection->{$streamName})) { return static::$_Collection->{$streamName}; } return false; } /** * Writes the given message and type to all of the configured log adapters. * Configured adapters are passed both the $type and $message variables. $type * is one of the following strings/values. * * ### Types: * * - LOG_EMERG => 'emergency', * - LOG_ALERT => 'alert', * - LOG_CRIT => 'critical', * - `LOG_ERR` => 'error', * - `LOG_WARNING` => 'warning', * - `LOG_NOTICE` => 'notice', * - `LOG_INFO` => 'info', * - `LOG_DEBUG` => 'debug', * * ### Usage: * * Write a message to the 'warning' log: * * `CakeLog::write('warning', 'Stuff is broken here');` * * @param int|string $type Type of message being written. When value is an integer * or a string matching the recognized levels, then it will * be treated log levels. Otherwise it's treated as scope. * @param string $message Message content to log * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success * @link https://book.cakephp.org/2.0/en/core-libraries/logging.html#writing-to-logs */ public static function write($type, $message, $scope = array()) { if (empty(static::$_Collection)) { static::_init(); } if (is_int($type) && isset(static::$_levels[$type])) { $type = static::$_levels[$type]; } if (is_string($type) && empty($scope) && !in_array($type, static::$_levels)) { $scope = $type; } $logged = false; foreach (static::$_Collection->enabled() as $streamName) { $logger = static::$_Collection->{$streamName}; $types = $scopes = $config = array(); if (method_exists($logger, 'config')) { $config = $logger->config(); } if (isset($config['types'])) { $types = $config['types']; } if (isset($config['scopes'])) { $scopes = $config['scopes']; } $inScope = (count(array_intersect((array)$scope, $scopes)) > 0); $correctLevel = in_array($type, $types); if ( // No config is a catch all (bc mode) (empty($types) && empty($scopes)) || // BC layer for mixing scope & level (in_array($type, $scopes)) || // no scopes, but has level (empty($scopes) && $correctLevel) || // exact scope + level ($correctLevel && $inScope) ) { $logger->write($type, $message); $logged = true; } } return $logged; } /** * Convenience method to log emergency messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function emergency($message, $scope = array()) { return static::write(static::$_levelMap['emergency'], $message, $scope); } /** * Convenience method to log alert messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function alert($message, $scope = array()) { return static::write(static::$_levelMap['alert'], $message, $scope); } /** * Convenience method to log critical messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function critical($message, $scope = array()) { return static::write(static::$_levelMap['critical'], $message, $scope); } /** * Convenience method to log error messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function error($message, $scope = array()) { return static::write(static::$_levelMap['error'], $message, $scope); } /** * Convenience method to log warning messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function warning($message, $scope = array()) { return static::write(static::$_levelMap['warning'], $message, $scope); } /** * Convenience method to log notice messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function notice($message, $scope = array()) { return static::write(static::$_levelMap['notice'], $message, $scope); } /** * Convenience method to log debug messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function debug($message, $scope = array()) { return static::write(static::$_levelMap['debug'], $message, $scope); } /** * Convenience method to log info messages * * @param string $message log message * @param string|array $scope The scope(s) a log message is being created in. * See CakeLog::config() for more information on logging scopes. * @return bool Success */ public static function info($message, $scope = array()) { return static::write(static::$_levelMap['info'], $message, $scope); } } ZoneMinder-1.32.2/web/api/lib/Cake/Log/Engine/0000755000000000000000000000000013365153155017243 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Log/Engine/SyslogLog.php0000644000000000000000000001031013365153155021671 0ustar rootroot 'Syslog', * 'types' => array('emergency', 'alert', 'critical', 'error'), * 'format' => "%s: My-App - %s", * 'prefix' => 'Web Server 01' * )); * ``` * * @var array */ protected $_defaults = array( 'format' => '%s: %s', 'flag' => LOG_ODELAY, 'prefix' => '', 'facility' => LOG_USER ); /** * Used to map the string names back to their LOG_* constants * * @var array */ protected $_priorityMap = array( 'emergency' => LOG_EMERG, 'alert' => LOG_ALERT, 'critical' => LOG_CRIT, 'error' => LOG_ERR, 'warning' => LOG_WARNING, 'notice' => LOG_NOTICE, 'info' => LOG_INFO, 'debug' => LOG_DEBUG ); /** * Whether the logger connection is open or not * * @var bool */ protected $_open = false; /** * Make sure the configuration contains the format parameter, by default it uses * the error number and the type as a prefix to the message * * @param array $config Options list. */ public function __construct($config = array()) { $config += $this->_defaults; parent::__construct($config); } /** * Writes a message to syslog * * Map the $type back to a LOG_ constant value, split multi-line messages into multiple * log messages, pass all messages through the format defined in the configuration * * @param string $type The type of log you are making. * @param string $message The message you want to log. * @return bool success of write. */ public function write($type, $message) { if (!$this->_open) { $config = $this->_config; $this->_open($config['prefix'], $config['flag'], $config['facility']); $this->_open = true; } $priority = LOG_DEBUG; if (isset($this->_priorityMap[$type])) { $priority = $this->_priorityMap[$type]; } $messages = explode("\n", $message); foreach ($messages as $message) { $message = sprintf($this->_config['format'], $type, $message); $this->_write($priority, $message); } return true; } /** * Extracts the call to openlog() in order to run unit tests on it. This function * will initialize the connection to the system logger * * @param string $ident the prefix to add to all messages logged * @param int $options the options flags to be used for logged messages * @param int $facility the stream or facility to log to * @return void */ protected function _open($ident, $options, $facility) { openlog($ident, $options, $facility); } /** * Extracts the call to syslog() in order to run unit tests on it. This function * will perform the actual write in the system logger * * @param int $priority Message priority. * @param string $message Message to log. * @return bool */ protected function _write($priority, $message) { return syslog($priority, $message); } /** * Closes the logger connection */ public function __destruct() { closelog(); } } ZoneMinder-1.32.2/web/api/lib/Cake/Log/Engine/BaseLog.php0000644000000000000000000000323013365153155021266 0ustar rootrootconfig($config); } /** * Sets instance config. When $config is null, returns config array * * Config * * - `types` string or array, levels the engine is interested in * - `scopes` string or array, scopes the engine is interested in * * @param array $config engine configuration * @return array */ public function config($config = array()) { if (!empty($config)) { foreach (array('types', 'scopes') as $option) { if (isset($config[$option]) && is_string($config[$option])) { $config[$option] = array($config[$option]); } } $this->_config = $config; } return $this->_config; } } ZoneMinder-1.32.2/web/api/lib/Cake/Log/Engine/FileLog.php0000644000000000000000000001312513365153155021277 0ustar rootroot LOGS, 'file' => null, 'types' => null, 'scopes' => array(), 'rotate' => 10, 'size' => 10485760, // 10MB 'mask' => null, ); /** * Path to save log files on. * * @var string */ protected $_path = null; /** * Log file name * * @var string */ protected $_file = null; /** * Max file size, used for log file rotation. * * @var int */ protected $_size = null; /** * Constructs a new File Logger. * * Config * * - `types` string or array, levels the engine is interested in * - `scopes` string or array, scopes the engine is interested in * - `file` Log file name * - `path` The path to save logs on. * - `size` Used to implement basic log file rotation. If log file size * reaches specified size the existing file is renamed by appending timestamp * to filename and new log file is created. Can be integer bytes value or * human reabable string values like '10MB', '100KB' etc. * - `rotate` Log files are rotated specified times before being removed. * If value is 0, old versions are removed rather then rotated. * - `mask` A mask is applied when log files are created. Left empty no chmod * is made. * * @param array $config Options for the FileLog, see above. */ public function __construct($config = array()) { $config = Hash::merge($this->_defaults, $config); parent::__construct($config); } /** * Sets protected properties based on config provided * * @param array $config Engine configuration * @return array */ public function config($config = array()) { parent::config($config); if (!empty($config['path'])) { $this->_path = $config['path']; } if (Configure::read('debug') && !is_dir($this->_path)) { mkdir($this->_path, 0775, true); } if (!empty($config['file'])) { $this->_file = $config['file']; if (substr($this->_file, -4) !== '.log') { $this->_file .= '.log'; } } if (!empty($config['size'])) { if (is_numeric($config['size'])) { $this->_size = (int)$config['size']; } else { $this->_size = CakeNumber::fromReadableSize($config['size']); } } return $this->_config; } /** * Implements writing to log files. * * @param string $type The type of log you are making. * @param string $message The message you want to log. * @return bool success of write. */ public function write($type, $message) { $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; $filename = $this->_getFilename($type); if (!empty($this->_size)) { $this->_rotateFile($filename); } $pathname = $this->_path . $filename; if (empty($this->_config['mask'])) { return file_put_contents($pathname, $output, FILE_APPEND); } $exists = file_exists($pathname); $result = file_put_contents($pathname, $output, FILE_APPEND); static $selfError = false; if (!$selfError && !$exists && !chmod($pathname, (int)$this->_config['mask'])) { $selfError = true; trigger_error(__d( 'cake_dev', 'Could not apply permission mask "%s" on log file "%s"', array($this->_config['mask'], $pathname)), E_USER_WARNING); $selfError = false; } return $result; } /** * Get filename * * @param string $type The type of log. * @return string File name */ protected function _getFilename($type) { $debugTypes = array('notice', 'info', 'debug'); if (!empty($this->_file)) { $filename = $this->_file; } elseif ($type === 'error' || $type === 'warning') { $filename = 'error.log'; } elseif (in_array($type, $debugTypes)) { $filename = 'debug.log'; } else { $filename = $type . '.log'; } return $filename; } /** * Rotate log file if size specified in config is reached. * Also if `rotate` count is reached oldest file is removed. * * @param string $filename Log file name * @return mixed True if rotated successfully or false in case of error, otherwise null. * Void if file doesn't need to be rotated. */ protected function _rotateFile($filename) { $filepath = $this->_path . $filename; if (version_compare(PHP_VERSION, '5.3.0') >= 0) { clearstatcache(true, $filepath); } else { clearstatcache(); } if (!file_exists($filepath) || filesize($filepath) < $this->_size ) { return null; } if ($this->_config['rotate'] === 0) { $result = unlink($filepath); } else { $result = rename($filepath, $filepath . '.' . time()); } $files = glob($filepath . '.*'); if ($files) { $filesToDelete = count($files) - $this->_config['rotate']; while ($filesToDelete > 0) { unlink(array_shift($files)); $filesToDelete--; } } return $result; } } ZoneMinder-1.32.2/web/api/lib/Cake/Log/Engine/ConsoleLog.php0000644000000000000000000000507413365153155022026 0ustar rootroot_output)) ) { $outputAs = ConsoleOutput::PLAIN; } else { $outputAs = ConsoleOutput::COLOR; } $config = Hash::merge(array( 'stream' => 'php://stderr', 'types' => null, 'scopes' => array(), 'outputAs' => $outputAs, ), $this->_config); $config = $this->config($config); if ($config['stream'] instanceof ConsoleOutput) { $this->_output = $config['stream']; } elseif (is_string($config['stream'])) { $this->_output = new ConsoleOutput($config['stream']); } else { throw new CakeLogException('`stream` not a ConsoleOutput nor string'); } $this->_output->outputAs($config['outputAs']); } /** * Implements writing to console. * * @param string $type The type of log you are making. * @param string $message The message you want to log. * @return bool success of write. */ public function write($type, $message) { $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; return $this->_output->write(sprintf('<%s>%s', $type, $output, $type), false); } } ZoneMinder-1.32.2/web/api/lib/Cake/Log/CakeLogInterface.php0000644000000000000000000000204713365153155021700 0ustar rootroot_getLogger($loggerName); $logger = new $className($options); if (!$logger instanceof CakeLogInterface) { throw new CakeLogException( __d('cake_dev', 'logger class %s does not implement a %s method.', $loggerName, 'write()') ); } $this->_loaded[$name] = $logger; if ($enable) { $this->enable($name); } return $logger; } /** * Attempts to import a logger class from the various paths it could be on. * Checks that the logger class implements a write method as well. * * @param string $loggerName the plugin.className of the logger class you want to build. * @return mixed boolean false on any failures, string of classname to use if search was successful. * @throws CakeLogException */ protected static function _getLogger($loggerName) { list($plugin, $loggerName) = pluginSplit($loggerName, true); if (substr($loggerName, -3) !== 'Log') { $loggerName .= 'Log'; } App::uses($loggerName, $plugin . 'Log/Engine'); if (!class_exists($loggerName)) { throw new CakeLogException(__d('cake_dev', 'Could not load class %s', $loggerName)); } return $loggerName; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/0000755000000000000000000000000013365153155016234 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/0000755000000000000000000000000013365153155017662 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/OverallFavoriteFixture.php0000644000000000000000000000254113365153155025050 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7198 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * OverallFavoriteFixture * * @package Cake.Test.Fixture */ class OverallFavoriteFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'model_type' => array('type' => 'string', 'length' => 255), 'model_id' => array('type' => 'integer'), 'priority' => array('type' => 'integer') ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'model_type' => 'Cd', 'model_id' => '1', 'priority' => '1'), array('id' => 2, 'model_type' => 'Book', 'model_id' => '1', 'priority' => '2') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/NodeFixture.php0000644000000000000000000000236513365153155022635 0ustar rootroot array('type' => 'integer', 'key' => 'primary'), 'name' => 'string', 'state' => 'integer' ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'name' => 'First', 'state' => 50), array('id' => 2, 'name' => 'Second', 'state' => 60), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TranslatedItemFixture.php0000644000000000000000000000255013365153155024664 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5669 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TranslatedItemFixture * * @package Cake.Test.Fixture */ class TranslatedItemFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'translated_article_id' => array('type' => 'integer'), 'slug' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('translated_article_id' => 1, 'slug' => 'first_translated'), array('translated_article_id' => 1, 'slug' => 'second_translated'), array('translated_article_id' => 1, 'slug' => 'third_translated') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/JoinAFixture.php0000644000000000000000000000316013365153155022742 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6317 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * JoinAFixture * * @package Cake.Test.Fixture */ class JoinAFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'default' => ''), 'body' => array('type' => 'text'), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('name' => 'Join A 1', 'body' => 'Join A 1 Body', 'created' => '2008-01-03 10:54:23', 'updated' => '2008-01-03 10:54:23'), array('name' => 'Join A 2', 'body' => 'Join A 2 Body', 'created' => '2008-01-03 10:54:24', 'updated' => '2008-01-03 10:54:24'), array('name' => 'Join A 3', 'body' => 'Join A 2 Body', 'created' => '2008-01-03 10:54:25', 'updated' => '2008-01-03 10:54:24') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidNativeTreeFixture.php0000644000000000000000000000236613365153155024646 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7984 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidNativeTreeFixture class * * @uses CakeTestFixture * @package Cake.Test.Fixture */ class UuidNativeTreeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'uuid', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'parent_id' => array('type' => 'string', 'length' => 36, 'null' => true), 'lft' => array('type' => 'integer', 'null' => false), 'rght' => array('type' => 'integer', 'null' => false) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/FeaturedFixture.php0000644000000000000000000000330713365153155023504 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class FeaturedFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'article_featured_id' => array('type' => 'integer', 'null' => false), 'category_id' => array('type' => 'integer', 'null' => false), 'published_date' => 'datetime', 'end_date' => 'datetime', 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/sample.xml0000644000000000000000000000020413365153155021661 0ustar rootroot๏ปฟ defect enhancement ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DatatypeFixture.php0000644000000000000000000000324213365153155023516 0ustar rootroot array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), 'float_field' => array('type' => 'float', 'length' => '5,2', 'null' => false, 'default' => null), 'decimal_field' => array('type' => 'decimal', 'length' => '6,3', 'default' => '0.000'), 'huge_int' => array('type' => 'biginteger'), 'normal_int' => array('type' => 'integer'), 'small_int' => array('type' => 'smallinteger'), 'tiny_int' => array('type' => 'tinyinteger'), 'bool' => array('type' => 'boolean', 'null' => false, 'default' => false), ); /** * Records property * * @var array */ public $records = array( array( 'id' => 1, 'float_field' => 42.23, 'huge_int' => '9223372036854775807', 'normal_int' => 2147483647, 'small_int' => 32767, 'tiny_int' => 127, 'bool' => 0 ), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TranslateTableFixture.php0000644000000000000000000000326613365153155024656 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5669 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TranslateTableFixture * * @package Cake.Test.Fixture */ class TranslateTableFixture extends CakeTestFixture { /** * table property * * @var string */ public $table = 'another_i18n'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'locale' => array('type' => 'string', 'length' => 6, 'null' => false), 'model' => array('type' => 'string', 'null' => false), 'foreign_key' => array('type' => 'integer', 'null' => false), 'field' => array('type' => 'string', 'null' => false), 'content' => array('type' => 'text')); /** * records property * * @var array */ public $records = array( array('locale' => 'eng', 'model' => 'TranslatedItemWithTable', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Another Title #1'), array('locale' => 'eng', 'model' => 'TranslatedItemWithTable', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Another Content #1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ProductFixture.php0000644000000000000000000000341613365153155023366 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ProductFixture * * @package Cake.Test.Fixture */ class ProductFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false), 'type' => array('type' => 'string', 'length' => 255, 'null' => false), 'price' => array('type' => 'integer', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('name' => 'Park\'s Great Hits', 'type' => 'Music', 'price' => 19), array('name' => 'Silly Puddy', 'type' => 'Toy', 'price' => 3), array('name' => 'Playstation', 'type' => 'Toy', 'price' => 89), array('name' => 'Men\'s T-Shirt', 'type' => 'Clothing', 'price' => 32), array('name' => 'Blouse', 'type' => 'Clothing', 'price' => 34), array('name' => 'Electronica 2002', 'type' => 'Music', 'price' => 4), array('name' => 'Country Tunes', 'type' => 'Music', 'price' => 21), array('name' => 'Watermelon', 'type' => 'Food', 'price' => 9) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/SessionFixture.php0000644000000000000000000000225013365153155023364 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * SessionFixture * * @package Cake.Test.Fixture */ class SessionFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 128, 'key' => 'primary'), 'data' => array('type' => 'text', 'null' => true), 'expires' => array('type' => 'integer', 'length' => 11, 'null' => true) ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ContentAccountFixture.php0000644000000000000000000000273213365153155024675 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ContentAccountFixture extends CakeTestFixture { public $table = 'ContentAccounts'; /** * fields property * * @var array */ public $fields = array( 'iContentAccountsId' => array('type' => 'integer', 'key' => 'primary'), 'iContentId' => array('type' => 'integer'), 'iAccountId' => array('type' => 'integer') ); /** * records property * * @var array */ public $records = array( array('iContentId' => 1, 'iAccountId' => 1), array('iContentId' => 2, 'iAccountId' => 2), array('iContentId' => 3, 'iAccountId' => 3), array('iContentId' => 4, 'iAccountId' => 4), array('iContentId' => 1, 'iAccountId' => 2), array('iContentId' => 2, 'iAccountId' => 3), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/rss.xml0000644000000000000000000000307213365153155021215 0ustar rootroot The Bakery: https://bakery.cakephp.org/ Recent Articles at The Bakery. en-us Wed, 01 Sep 2010 12:09:25 -0500 http://validator.w3.org/feed/docs/rss2.html CakePHP Bakery mariano@cricava.com (Mariano Iglesias) gwoo@cakephp.org (Garrett Woodworth) EpisodeCMS https://bakery.cakephp.org/articles/view/episodecms EpisodeCMS is CakePHP based content management system. Features: control panel, events API, module management, multilanguage and translations, themes http://episodecms.com/ Please help me to improve it. Thanks. Tue, 31 Aug 2010 02:07:02 -0500 https://bakery.cakephp.org/articles/view/episodecms Alertpay automated sales via IPN https://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn I'm going to show you how I implemented a payment module via the Alertpay payment processor. Tue, 31 Aug 2010 01:42:00 -0500 https://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArmorFixture.php0000644000000000000000000000301013365153155023014 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArmorFixture extends CakeTestFixture { /** * Datasource * * Used for Multi database fixture test * * @var string */ public $useDbConfig = 'test2'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('name' => 'Leather', 'created' => '2007-03-17 01:16:23'), array('name' => 'Chainmail', 'created' => '2007-03-17 01:18:23'), array('name' => 'Cloak', 'created' => '2007-03-17 01:20:23'), array('name' => 'Bikini', 'created' => '2007-03-17 01:22:23'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/SecondaryModelFixture.php0000644000000000000000000000224113365153155024651 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * SecondaryModelFixture * * @package Cake.Test.Fixture */ class SecondaryModelFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'secondary_name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('secondary_name' => 'Secondary Name Existing') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TagFixture.php0000644000000000000000000000301513365153155022454 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TagFixture * * @package Cake.Test.Fixture */ class TagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'tag' => array('type' => 'string', 'null' => false), 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), ); /** * records property * * @var array */ public $records = array( array('tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'), array('tag' => 'tag2', 'created' => '2007-03-18 12:24:23', 'updated' => '2007-03-18 12:26:31'), array('tag' => 'tag3', 'created' => '2007-03-18 12:26:23', 'updated' => '2007-03-18 12:28:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidportfolioFixture.php0000644000000000000000000000242113365153155024605 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidportfolioFixture * * @package Cake.Test.Fixture */ class UuidportfolioFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 36, 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('id' => '4806e091-6940-4d2b-b227-303740cf8569', 'name' => 'Portfolio 1'), array('id' => '480af662-eb8c-47d3-886b-230540cf8569', 'name' => 'Portfolio 2'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CounterCacheUserFixture.php0000644000000000000000000000251613365153155025150 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CounterCacheUserFixture extends CakeTestFixture { public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false), 'post_count' => array('type' => 'integer', 'null' => true), 'posts_published' => array('type' => 'integer', 'null' => true) ); public $records = array( array('id' => 66, 'name' => 'Alexander', 'post_count' => 2, 'posts_published' => 1), array('id' => 301, 'name' => 'Steven', 'post_count' => 1, 'posts_published' => 1), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/HomeFixture.php0000644000000000000000000000311313365153155022630 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * HomeFixture * * @package Cake.Test.Fixture */ class HomeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'another_article_id' => array('type' => 'integer', 'null' => false), 'advertisement_id' => array('type' => 'integer', 'null' => false), 'title' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('another_article_id' => 1, 'advertisement_id' => 1, 'title' => 'First Home', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('another_article_id' => 3, 'advertisement_id' => 1, 'title' => 'Second Home', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuiditemsUuidportfolioFixture.php0000644000000000000000000000365113365153155026504 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuiditemsUuidportfolioFixture * * @package Cake.Test.Fixture */ class UuiditemsUuidportfolioFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 36, 'key' => 'primary'), 'uuiditem_id' => array('type' => 'string', 'length' => 36, 'null' => false), 'uuidportfolio_id' => array('type' => 'string', 'length' => 36, 'null' => false) ); /** * records property * * @var array */ public $records = array( array('id' => '4850fd8f-cc5c-449f-bf34-0c5240cf8569', 'uuiditem_id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'uuidportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('id' => '4850fee5-d24c-4ea0-9759-0c2e40cf8569', 'uuiditem_id' => '48298a29-81c0-4c26-a7fb-413140cf8569', 'uuidportfolio_id' => '480af662-eb8c-47d3-886b-230540cf8569'), array('id' => '4851af6e-fa18-403d-b57e-437d40cf8569', 'uuiditem_id' => '482b7756-8da0-419a-b21f-27da40cf8569', 'uuidportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('id' => '4851b94c-9790-42dc-b760-4f9240cf8569', 'uuiditem_id' => '482cfd4b-0e7c-4ea3-9582-4cec40cf8569', 'uuidportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ProductUpdateAllFixture.php0000644000000000000000000000332713365153155025163 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ProductUpdateAllFixture * * @package Cake.Test.Fixture */ class ProductUpdateAllFixture extends CakeTestFixture { public $table = 'product_update_all'; public $fields = array( 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false, 'length' => 29), 'groupcode' => array('type' => 'integer', 'null' => false, 'length' => 4), 'group_id' => array('type' => 'integer', 'null' => false, 'length' => 8), 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) ); public $records = array( array( 'id' => 1, 'name' => 'product one', 'groupcode' => 120, 'group_id' => 1 ), array( 'id' => 2, 'name' => 'product two', 'groupcode' => 120, 'group_id' => 1 ), array( 'id' => 3, 'name' => 'product three', 'groupcode' => 125, 'group_id' => 2 ), array( 'id' => 4, 'name' => 'product four', 'groupcode' => 135, 'group_id' => 4 ), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/NumericArticleFixture.php0000644000000000000000000000255613365153155024660 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * NumericArticleFixture * * @package Cake.Test.Fixture */ class NumericArticleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('title' => 'First Article', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('title' => '12345abcde', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AfterTreeFixture.php0000644000000000000000000000321413365153155023623 0ustar rootroot array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer'), 'lft' => array('type' => 'integer'), 'rght' => array('type' => 'integer'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false) ); /** * records property * * @var array */ public $records = array( array('parent_id' => null, 'lft' => 1, 'rght' => 2, 'name' => 'One'), array('parent_id' => null, 'lft' => 3, 'rght' => 4, 'name' => 'Two'), array('parent_id' => null, 'lft' => 5, 'rght' => 6, 'name' => 'Three'), array('parent_id' => null, 'lft' => 7, 'rght' => 12, 'name' => 'Four'), array('parent_id' => null, 'lft' => 8, 'rght' => 9, 'name' => 'Five'), array('parent_id' => null, 'lft' => 10, 'rght' => 11, 'name' => 'Six'), array('parent_id' => null, 'lft' => 13, 'rght' => 14, 'name' => 'Seven') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UserFixture.php0000644000000000000000000000342713365153155022666 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UserFixture * * @package Cake.Test.Fixture */ class UserFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user' => array('type' => 'string', 'null' => true), 'password' => array('type' => 'string', 'null' => true), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31'), array('user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TranslatedArticleFixture.php0000644000000000000000000000315413365153155025352 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5669 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TranslatedArticleFixture * * @package Cake.Test.Fixture */ class TranslatedArticleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user_id' => array('type' => 'integer', 'null' => false), 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'user_id' => 1, 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('id' => 2, 'user_id' => 3, 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('id' => 3, 'user_id' => 1, 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/SyfileFixture.php0000644000000000000000000000275413365153155023205 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * SyfileFixture * * @package Cake.Test.Fixture */ class SyfileFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'image_id' => array('type' => 'integer', 'null' => true), 'name' => array('type' => 'string', 'null' => false), 'item_count' => array('type' => 'integer', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('image_id' => 1, 'name' => 'Syfile 1'), array('image_id' => 2, 'name' => 'Syfile 2'), array('image_id' => 5, 'name' => 'Syfile 3'), array('image_id' => 3, 'name' => 'Syfile 4'), array('image_id' => 4, 'name' => 'Syfile 5'), array('image_id' => null, 'name' => 'Syfile 6') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/PostsTagFixture.php0000644000000000000000000000255613365153155023516 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * PostsTagFixture * * @package Cake.Test.Fixture */ class PostsTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'post_id' => array('type' => 'integer', 'null' => false), 'tag_id' => array('type' => 'string', 'null' => false), 'indexes' => array('posts_tag' => array('column' => array('tag_id', 'post_id'), 'unique' => 1)) ); /** * records property * * @var array */ public $records = array( array('post_id' => 1, 'tag_id' => 'tag1'), array('post_id' => 1, 'tag_id' => 'tag2'), array('post_id' => 2, 'tag_id' => 'tag1'), array('post_id' => 2, 'tag_id' => 'tag3') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TranslateArticleFixture.php0000644000000000000000000000730013365153155025203 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5669 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TranslateArticleFixture * * @package Cake.Test.Fixture */ class TranslateArticleFixture extends CakeTestFixture { /** * table property * * @var string */ public $table = 'article_i18n'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'locale' => array('type' => 'string', 'length' => 6, 'null' => false), 'model' => array('type' => 'string', 'null' => false), 'foreign_key' => array('type' => 'integer', 'null' => false), 'field' => array('type' => 'string', 'null' => false), 'content' => array('type' => 'text') ); /** * records property * * @var array */ public $records = array( array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (eng) #1'), array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (eng) #1'), array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (deu) #1'), array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (deu) #1'), array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (cze) #1'), array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (cze) #1'), array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (eng) #2'), array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (eng) #2'), array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (deu) #2'), array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (deu) #2'), array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (cze) #2'), array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (cze) #2'), array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (eng) #3'), array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (eng) #3'), array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (deu) #3'), array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (deu) #3'), array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (cze) #3'), array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (cze) #3') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BakeArticleFixture.php0000644000000000000000000000245313365153155024114 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BakeArticleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'bake_user_id' => array('type' => 'integer', 'null' => false), 'title' => array('type' => 'string', 'null' => false), 'body' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuiditemFixture.php0000644000000000000000000000333213365153155023530 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuiditemFixture * * @package Cake.Test.Fixture */ class UuiditemFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 36, 'key' => 'primary'), 'published' => array('type' => 'boolean', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'published' => 0, 'name' => 'Item 1'), array('id' => '48298a29-81c0-4c26-a7fb-413140cf8569', 'published' => 0, 'name' => 'Item 2'), array('id' => '482b7756-8da0-419a-b21f-27da40cf8569', 'published' => 0, 'name' => 'Item 3'), array('id' => '482cfd4b-0e7c-4ea3-9582-4cec40cf8569', 'published' => 0, 'name' => 'Item 4'), array('id' => '4831181b-4020-4983-a29b-131440cf8569', 'published' => 0, 'name' => 'Item 5'), array('id' => '483798c8-c7cc-430e-8cf9-4fcc40cf8569', 'published' => 0, 'name' => 'Item 6') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArmorsPlayerFixture.php0000644000000000000000000000314513365153155024365 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArmorsPlayerFixture extends CakeTestFixture { /** * Datasource * * Used for Multi database fixture test * * @var string */ public $useDbConfig = 'test_database_three'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'player_id' => array('type' => 'integer', 'null' => false), 'armor_id' => array('type' => 'integer', 'null' => false), 'broken' => array('type' => 'boolean', 'null' => false, 'default' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('player_id' => 1, 'armor_id' => 1, 'broken' => false), array('player_id' => 2, 'armor_id' => 2, 'broken' => false), array('player_id' => 3, 'armor_id' => 3, 'broken' => false), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AppleFixture.php0000644000000000000000000000530113365153155023002 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AppleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'apple_id' => array('type' => 'integer', 'null' => true), 'color' => array('type' => 'string', 'length' => 40, 'null' => false), 'name' => array('type' => 'string', 'length' => 40, 'null' => false), 'created' => 'datetime', 'date' => 'date', 'modified' => 'datetime', 'mytime' => 'time' ); /** * records property * * @var array */ public $records = array( array('apple_id' => 2, 'color' => 'Red 1', 'name' => 'Red Apple 1', 'created' => '2006-11-22 10:38:58', 'date' => '1951-01-04', 'modified' => '2006-12-01 13:31:26', 'mytime' => '22:57:17'), array('apple_id' => 1, 'color' => 'Bright Red 1', 'name' => 'Bright Red Apple', 'created' => '2006-11-22 10:43:13', 'date' => '2014-01-01', 'modified' => '2006-11-30 18:38:10', 'mytime' => '22:57:17'), array('apple_id' => 2, 'color' => 'blue green', 'name' => 'green blue', 'created' => '2006-12-25 05:13:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:24', 'mytime' => '22:57:17'), array('apple_id' => 2, 'color' => 'Blue Green', 'name' => 'Test Name', 'created' => '2006-12-25 05:23:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:36', 'mytime' => '22:57:17'), array('apple_id' => 5, 'color' => 'Green', 'name' => 'Blue Green', 'created' => '2006-12-25 05:24:06', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:16', 'mytime' => '22:57:17'), array('apple_id' => 4, 'color' => 'My new appleOrange', 'name' => 'My new apple', 'created' => '2006-12-25 05:29:39', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:39', 'mytime' => '22:57:17'), array('apple_id' => 6, 'color' => 'Some wierd color', 'name' => 'Some odd color', 'created' => '2006-12-25 05:34:21', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:34:21', 'mytime' => '22:57:17') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CategoryThreadFixture.php0000644000000000000000000000411113365153155024644 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CategoryThreadFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('parent_id' => 0, 'name' => 'Category 1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 1, 'name' => 'Category 1.1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 2, 'name' => 'Category 1.1.1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 3, 'name' => 'Category 1.1.2', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 4, 'name' => 'Category 1.1.1.1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 5, 'name' => 'Category 2', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 6, 'name' => 'Category 2.1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BiddingFixture.php0000644000000000000000000000250513365153155023304 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.3.14 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BiddingFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'bid' => array('type' => 'string', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('bid' => 'One', 'name' => 'Bid 1'), array('bid' => 'Two', 'name' => 'Bid 2'), array('bid' => 'Three', 'name' => 'Bid 3'), array('bid' => 'Five', 'name' => 'Bid 5') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/PostFixture.php0000644000000000000000000000345713365153155022700 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Clas PostFixture * * @package Cake.Test.Fixture */ class PostFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'author_id' => array('type' => 'integer', 'null' => false), 'title' => array('type' => 'string', 'null' => false), 'body' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('author_id' => 1, 'title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('author_id' => 3, 'title' => 'Second Post', 'body' => 'Second Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('author_id' => 1, 'title' => 'Third Post', 'body' => 'Third Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BinaryTestFixture.php0000644000000000000000000000214113365153155024024 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6700 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BinaryTestFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'data' => array('type' => 'binary', 'length' => 300) ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TranslateFixture.php0000644000000000000000000000707313365153155023706 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5669 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TranslateFixture * * @package Cake.Test.Fixture */ class TranslateFixture extends CakeTestFixture { /** * table property * * @var string */ public $table = 'i18n'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'locale' => array('type' => 'string', 'length' => 6, 'null' => false), 'model' => array('type' => 'string', 'null' => false), 'foreign_key' => array('type' => 'integer', 'null' => false), 'field' => array('type' => 'string', 'null' => false), 'content' => array('type' => 'text') ); /** * records property * * @var array */ public $records = array( array('locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), array('locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), array('locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), array('locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), array('locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1'), array('locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Obsah #1'), array('locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title #2'), array('locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Content #2'), array('locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Titel #2'), array('locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Inhalt #2'), array('locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Titulek #2'), array('locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Obsah #2'), array('locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title #3'), array('locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Content #3'), array('locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Titel #3'), array('locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Inhalt #3'), array('locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Titulek #3'), array('locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Obsah #3') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ItemFixture.php0000644000000000000000000000311513365153155022640 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ItemFixture * * @package Cake.Test.Fixture */ class ItemFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'syfile_id' => array('type' => 'integer', 'null' => false), 'published' => array('type' => 'boolean', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('syfile_id' => 1, 'published' => 0, 'name' => 'Item 1'), array('syfile_id' => 2, 'published' => 0, 'name' => 'Item 2'), array('syfile_id' => 3, 'published' => 0, 'name' => 'Item 3'), array('syfile_id' => 4, 'published' => 0, 'name' => 'Item 4'), array('syfile_id' => 5, 'published' => 0, 'name' => 'Item 5'), array('syfile_id' => 6, 'published' => 0, 'name' => 'Item 6') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ImageFixture.php0000644000000000000000000000234313365153155022766 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ImageFixture * * @package Cake.Test.Fixture */ class ImageFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('name' => 'Image 1'), array('name' => 'Image 2'), array('name' => 'Image 3'), array('name' => 'Image 4'), array('name' => 'Image 5') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BidFixture.php0000644000000000000000000000261713365153155022446 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BidFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'message_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('message_id' => 1, 'name' => 'Bid 1.1'), array('message_id' => 1, 'name' => 'Bid 1.2'), array('message_id' => 3, 'name' => 'Bid 3.1'), array('message_id' => 2, 'name' => 'Bid 2.1'), array('message_id' => 2, 'name' => 'Bid 2.2') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/SampleFixture.php0000644000000000000000000000253013365153155023163 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * SampleFixture * * @package Cake.Test.Fixture */ class SampleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'apple_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'length' => 40, 'null' => false) ); /** * records property * * @var array */ public $records = array( array('apple_id' => 3, 'name' => 'sample1'), array('apple_id' => 2, 'name' => 'sample2'), array('apple_id' => 4, 'name' => 'sample3'), array('apple_id' => 5, 'name' => 'sample4') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidNativeFixture.php0000644000000000000000000000353013365153155024020 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6700 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidNativeFixture. * * @package Cake.Test.Fixture */ class UuidNativeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'uuid', 'key' => 'primary'), 'title' => 'string', 'count' => array('type' => 'integer', 'default' => 0), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('id' => '47c36f9c-bc00-4d17-9626-4e183ca6822b', 'title' => 'Unique record 1', 'count' => 2, 'created' => '2008-03-13 01:16:23', 'updated' => '2008-03-13 01:18:31'), array('id' => '47c36f9c-f2b0-43f5-b3f7-4e183ca6822b', 'title' => 'Unique record 2', 'count' => 4, 'created' => '2008-03-13 01:18:24', 'updated' => '2008-03-13 01:20:32'), array('id' => '47c36f9c-0ffc-4084-9b03-4e183ca6822b', 'title' => 'Unique record 3', 'count' => 5, 'created' => '2008-03-13 01:20:25', 'updated' => '2008-03-13 01:22:33'), array('id' => '47c36f9c-2578-4c2e-aeab-4e183ca6822b', 'title' => 'Unique record 4', 'count' => 3, 'created' => '2008-03-13 01:22:26', 'updated' => '2008-03-13 01:24:34'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DeviceFixture.php0000644000000000000000000000264013365153155023143 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DeviceFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'device_type_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false), 'typ' => array('type' => 'integer', 'null' => false), ); /** * records property * * @var array */ public $records = array( array('device_type_id' => 1, 'name' => 'Device 1', 'typ' => 1), array('device_type_id' => 1, 'name' => 'Device 2', 'typ' => 1), array('device_type_id' => 1, 'name' => 'Device 3', 'typ' => 2) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidNativeTagFixture.php0000644000000000000000000000236613365153155024462 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7953 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidNativeTagFixture * * @package Cake.Test.Fixture */ class UuidNativeTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'uuid', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255), 'created' => array('type' => 'datetime') ); /** * records property * * @var array */ public $records = array( array('id' => '481fc6d0-b920-43e0-e50f-6d1740cf8569', 'name' => 'MyTag', 'created' => '2009-12-09 12:30:00') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AnotherArticleFixture.php0000644000000000000000000000274513365153155024656 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AnotherArticleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('title' => 'First Article', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('title' => 'Second Article', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('title' => 'Third Article', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/PersonFixture.php0000644000000000000000000000365013365153155023214 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6700 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * PersonFixture * * @package Cake.Test.Fixture */ class PersonFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'null' => false, 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false, 'length' => 32), 'mother_id' => array('type' => 'integer', 'null' => false, 'key' => 'index'), 'father_id' => array('type' => 'integer', 'null' => false), 'indexes' => array( 'PRIMARY' => array('column' => 'id', 'unique' => 1), 'mother_id' => array('column' => array('mother_id', 'father_id'), 'unique' => 0) ) ); /** * records property * * @var array */ public $records = array( array('name' => 'person', 'mother_id' => 2, 'father_id' => 3), array('name' => 'mother', 'mother_id' => 4, 'father_id' => 5), array('name' => 'father', 'mother_id' => 6, 'father_id' => 7), array('name' => 'mother - grand mother', 'mother_id' => 0, 'father_id' => 0), array('name' => 'mother - grand father', 'mother_id' => 0, 'father_id' => 0), array('name' => 'father - grand mother', 'mother_id' => 0, 'father_id' => 0), array('name' => 'father - grand father', 'mother_id' => 0, 'father_id' => 0) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AttachmentFixture.php0000644000000000000000000000254313365153155024036 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AttachmentFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'comment_id' => array('type' => 'integer', 'null' => false), 'attachment' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('comment_id' => 5, 'attachment' => 'attachment.zip', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AcoTwoFixture.php0000644000000000000000000000510513365153155023137 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AcoTwoFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'model' => array('type' => 'string', 'null' => true), 'foreign_key' => array('type' => 'integer', 'length' => 10, 'null' => true), 'alias' => array('type' => 'string', 'default' => ''), 'lft' => array('type' => 'integer', 'length' => 10, 'null' => true), 'rght' => array('type' => 'integer', 'length' => 10, 'null' => true) ); /** * records property * * @var array */ public $records = array( array('parent_id' => null, 'model' => null, 'foreign_key' => null, 'alias' => 'ROOT', 'lft' => 1, 'rght' => 20), array('parent_id' => 1, 'model' => null, 'foreign_key' => null, 'alias' => 'tpsReports', 'lft' => 2, 'rght' => 9), array('parent_id' => 2, 'model' => null, 'foreign_key' => null, 'alias' => 'view', 'lft' => 3, 'rght' => 6), array('parent_id' => 3, 'model' => null, 'foreign_key' => null, 'alias' => 'current', 'lft' => 4, 'rght' => 5), array('parent_id' => 2, 'model' => null, 'foreign_key' => null, 'alias' => 'update', 'lft' => 7, 'rght' => 8), array('parent_id' => 1, 'model' => null, 'foreign_key' => null, 'alias' => 'printers', 'lft' => 10, 'rght' => 19), array('parent_id' => 6, 'model' => null, 'foreign_key' => null, 'alias' => 'print', 'lft' => 11, 'rght' => 14), array('parent_id' => 7, 'model' => null, 'foreign_key' => null, 'alias' => 'lettersize', 'lft' => 12, 'rght' => 13), array('parent_id' => 6, 'model' => null, 'foreign_key' => null, 'alias' => 'refill', 'lft' => 15, 'rght' => 16), array('parent_id' => 6, 'model' => null, 'foreign_key' => null, 'alias' => 'smash', 'lft' => 17, 'rght' => 18), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/NumberTreeFixture.php0000644000000000000000000000250613365153155024015 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5331 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * NumberTreeFixture * * Generates a tree of data for use testing the tree behavior * * @package Cake.Test.Fixture */ class NumberTreeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'parent_id' => 'integer', 'lft' => array('type' => 'integer', 'null' => false), 'rght' => array('type' => 'integer', 'null' => false), 'level' => array('type' => 'integer', 'null' => true) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BakeTagFixture.php0000644000000000000000000000220013365153155023232 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BakeTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'tag' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BasketFixture.php0000644000000000000000000000265513365153155023163 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BasketFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'type' => array('type' => 'string', 'length' => 255), 'name' => array('type' => 'string', 'length' => 255), 'object_id' => array('type' => 'integer'), 'user_id' => array('type' => 'integer'), ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'type' => 'nonfile', 'name' => 'basket1', 'object_id' => 1, 'user_id' => 1), array('id' => 2, 'type' => 'file', 'name' => 'basket2', 'object_id' => 2, 'user_id' => 1), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ProjectFixture.php0000644000000000000000000000226113365153155023351 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ProjectFixture * * @package Cake.Test.Fixture */ class ProjectFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('name' => 'Project 1'), array('name' => 'Project 2'), array('name' => 'Project 3') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidTagFixture.php0000644000000000000000000000237413365153155023312 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7953 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidTagFixture * * @package Cake.Test.Fixture */ class UuidTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 36, 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255), 'created' => array('type' => 'datetime') ); /** * records property * * @var array */ public $records = array( array('id' => '481fc6d0-b920-43e0-e50f-6d1740cf8569', 'name' => 'MyTag', 'created' => '2009-12-09 12:30:00') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArosAcoTwoFixture.php0000644000000000000000000000732513365153155023772 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArosAcoTwoFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'aro_id' => array('type' => 'integer', 'length' => 10, 'null' => false), 'aco_id' => array('type' => 'integer', 'length' => 10, 'null' => false), '_create' => array('type' => 'string', 'length' => 2, 'default' => 0), '_read' => array('type' => 'string', 'length' => 2, 'default' => 0), '_update' => array('type' => 'string', 'length' => 2, 'default' => 0), '_delete' => array('type' => 'string', 'length' => 2, 'default' => 0) ); /** * records property * * @var array */ public $records = array( array('aro_id' => '1', 'aco_id' => '1', '_create' => '-1', '_read' => '-1', '_update' => '-1', '_delete' => '-1'), array('aro_id' => '2', 'aco_id' => '1', '_create' => '0', '_read' => '1', '_update' => '1', '_delete' => '1'), array('aro_id' => '3', 'aco_id' => '2', '_create' => '0', '_read' => '1', '_update' => '0', '_delete' => '0'), array('aro_id' => '4', 'aco_id' => '2', '_create' => '1', '_read' => '1', '_update' => '0', '_delete' => '-1'), array('aro_id' => '4', 'aco_id' => '6', '_create' => '1', '_read' => '1', '_update' => '0', '_delete' => '0'), array('aro_id' => '5', 'aco_id' => '1', '_create' => '1', '_read' => '1', '_update' => '1', '_delete' => '1'), array('aro_id' => '6', 'aco_id' => '3', '_create' => '-1', '_read' => '1', '_update' => '-1', '_delete' => '-1'), array('aro_id' => '6', 'aco_id' => '4', '_create' => '-1', '_read' => '1', '_update' => '-1', '_delete' => '1'), array('aro_id' => '6', 'aco_id' => '6', '_create' => '-1', '_read' => '1', '_update' => '1', '_delete' => '-1'), array('aro_id' => '7', 'aco_id' => '2', '_create' => '-1', '_read' => '-1', '_update' => '-1', '_delete' => '-1'), array('aro_id' => '7', 'aco_id' => '7', '_create' => '1', '_read' => '1', '_update' => '1', '_delete' => '0'), array('aro_id' => '7', 'aco_id' => '8', '_create' => '1', '_read' => '1', '_update' => '1', '_delete' => '0'), array('aro_id' => '7', 'aco_id' => '9', '_create' => '1', '_read' => '1', '_update' => '1', '_delete' => '1'), array('aro_id' => '7', 'aco_id' => '10', '_create' => '0', '_read' => '0', '_update' => '0', '_delete' => '1'), array('aro_id' => '8', 'aco_id' => '10', '_create' => '1', '_read' => '1', '_update' => '1', '_delete' => '1'), array('aro_id' => '8', 'aco_id' => '2', '_create' => '-1', '_read' => '-1', '_update' => '-1', '_delete' => '-1'), array('aro_id' => '9', 'aco_id' => '4', '_create' => '1', '_read' => '1', '_update' => '1', '_delete' => '-1'), array('aro_id' => '9', 'aco_id' => '9', '_create' => '0', '_read' => '0', '_update' => '1', '_delete' => '1'), array('aro_id' => '10', 'aco_id' => '9', '_create' => '1', '_read' => '1', '_update' => '1', '_delete' => '1'), array('aro_id' => '10', 'aco_id' => '10', '_create' => '-1', '_read' => '-1', '_update' => '-1', '_delete' => '-1'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AdvertisementFixture.php0000644000000000000000000000255513365153155024563 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AdvertisementFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('title' => 'First Ad', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('title' => 'Second Ad', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/soap_response.xml0000644000000000000000000000047713365153155023274 0ustar rootroot 34.5 ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DocumentDirectoryFixture.php0000644000000000000000000000222313365153155025404 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DocumentDirectoryFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('name' => 'DocumentDirectory 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/FixturizedTestCase.php0000644000000000000000000000226713365153155024173 0ustar rootrootassertInstanceOf('CakeFixtureManager', $this->fixtureManager); } /** * test that it is possible to load fixtures on demand * * @return void */ public function testFixtureLoadOnDemand() { $this->loadFixtures('Category'); } /** * test that a test is marked as skipped using skipIf and its first parameter evaluates to true * * @return void */ public function testSkipIfTrue() { $this->skipIf(true); } /** * test that a test is not marked as skipped using skipIf and its first parameter evaluates to false * * @return void */ public function testSkipIfFalse() { $this->skipIf(false); } /** * test that a fixtures are unoaded even if the test throws exceptions * * @return void * @throws Exception */ public function testThrowException() { throw new Exception(); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidTreeFixture.php0000644000000000000000000000237413365153155023476 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7984 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidTreeFixture class * * @uses CakeTestFixture * @package Cake.Test.Fixture */ class UuidTreeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 36, 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'parent_id' => array('type' => 'string', 'length' => 36, 'null' => true), 'lft' => array('type' => 'integer', 'null' => false), 'rght' => array('type' => 'integer', 'null' => false) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CdFixture.php0000644000000000000000000000251013365153155022266 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7198 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CdFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => 255), 'artist' => array('type' => 'string', 'length' => 255, 'null' => true), 'genre' => array('type' => 'string', 'length' => 255, 'null' => true) ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'title' => 'Grace', 'artist' => 'Jeff Buckley', 'genre' => 'awesome') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CallbackFixture.php0000644000000000000000000000322513365153155023440 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CallbackFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user' => array('type' => 'string', 'null' => false), 'password' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('user' => 'user1', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('user' => 'user2', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31'), array('user' => 'user3', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/MyCategoriesMyUsersFixture.php0000644000000000000000000000244513365153155025672 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * MyCategoriesMyUsersFixture * * @package Cake.Test.Fixture */ class MyCategoriesMyUsersFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'my_category_id' => array('type' => 'integer'), 'my_user_id' => array('type' => 'integer'), ); /** * records property * * @var array */ public $records = array( array('my_category_id' => 1, 'my_user_id' => 1), array('my_category_id' => 3, 'my_user_id' => 1), array('my_category_id' => 1, 'my_user_id' => 2), array('my_category_id' => 2, 'my_user_id' => 2), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/FeatureSetFixture.php0000644000000000000000000000220513365153155024010 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class FeatureSetFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('name' => 'FeatureSet 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/soap_request.xml0000644000000000000000000000046613365153155023124 0ustar rootroot IBM ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/SiteFixture.php0000644000000000000000000000270013365153155022645 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * SiteFixture * * @package Cake.Test.Fixture */ class SiteFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('name' => 'cakephp', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('name' => 'Mark Story\'s sites', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('name' => 'rchavik sites', 'created' => '2001-02-03 00:01:02', 'updated' => '2007-03-17 01:22:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/GuildsPlayerFixture.php0000644000000000000000000000247613365153155024357 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * GuildsPlayerFixture * * @package Cake.Test.Fixture */ class GuildsPlayerFixture extends CakeTestFixture { public $useDbConfig = 'test2'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'player_id' => array('type' => 'integer', 'null' => false), 'guild_id' => array('type' => 'integer', 'null' => false), ); /** * records property * * @var array */ public $records = array( array('player_id' => 1, 'guild_id' => 1), array('player_id' => 1, 'guild_id' => 2), array('player_id' => 4, 'guild_id' => 3), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CakeSessionFixture.php0000644000000000000000000000225213365153155024152 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.3.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Fixture class for the default session configuration * * @package Cake.Test.Fixture */ class CakeSessionFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 128, 'key' => 'primary'), 'data' => array('type' => 'text', 'null' => true), 'expires' => array('type' => 'integer', 'length' => 11, 'null' => true) ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UnsignedFixture.php0000644000000000000000000000417113365153155023521 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.5.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class UnsignedFixture extends CakeTestFixture { /** * table property * * @var array */ public $table = 'unsigned'; /** * fields property * * @var array */ public $fields = array( 'uinteger' => array('type' => 'integer', 'null' => '', 'default' => '1', 'length' => '8', 'key' => 'primary', 'unsigned' => true), 'integer' => array('type' => 'integer', 'length' => '8', 'unsigned' => false), 'usmallinteger' => array('type' => 'smallinteger', 'unsigned' => true), 'smallinteger' => array('type' => 'smallinteger', 'unsigned' => false), 'utinyinteger' => array('type' => 'tinyinteger', 'unsigned' => true), 'tinyinteger' => array('type' => 'tinyinteger', 'unsigned' => false), 'udecimal' => array('type' => 'decimal', 'length' => '4', 'unsigned' => true), 'decimal' => array('type' => 'decimal', 'length' => '4'), 'biginteger' => array('type' => 'biginteger', 'length' => '20', 'default' => 3), 'ubiginteger' => array('type' => 'biginteger', 'length' => '20', 'default' => 3, 'unsigned' => true), 'float' => array('type' => 'float', 'length' => '4'), 'ufloat' => array('type' => 'float', 'length' => '4', 'unsigned' => true), 'string' => array('type' => 'string', 'length' => '4'), 'tableParameters' => array( 'engine' => 'MyISAM' ) ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/PlayerFixture.php0000644000000000000000000000257413365153155023206 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * PlayerFixture * * @package Cake.Test.Fixture */ class PlayerFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('name' => 'mark', 'created' => '2007-03-17 01:16:23'), array('name' => 'jack', 'created' => '2007-03-17 01:18:23'), array('name' => 'larry', 'created' => '2007-03-17 01:20:23'), array('name' => 'jose', 'created' => '2007-03-17 01:22:23'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/GroupUpdateAllFixture.php0000644000000000000000000000302313365153155024630 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * GroupUpdateAllFixture * * @package Cake.Test.Fixture */ class GroupUpdateAllFixture extends CakeTestFixture { public $table = 'group_update_all'; public $fields = array( 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false, 'length' => 29), 'code' => array('type' => 'integer', 'null' => false, 'length' => 4), 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) ); public $records = array( array( 'id' => 1, 'name' => 'group one', 'code' => 120 ), array( 'id' => 2, 'name' => 'group two', 'code' => 125 ), array( 'id' => 3, 'name' => 'group three', 'code' => 130 ), array( 'id' => 4, 'name' => 'group four', 'code' => 135 ), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/PrimaryModelFixture.php0000644000000000000000000000222713365153155024351 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * PrimaryModelFixture * * @package Cake.Test.Fixture */ class PrimaryModelFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'primary_name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('primary_name' => 'Primary Name Existing') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidnativeportfolioFixture.php0000644000000000000000000000241313365153155026015 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidnativeportfolioFixture * * @package Cake.Test.Fixture */ class UuidnativeportfolioFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'uuid', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('id' => '4806e091-6940-4d2b-b227-303740cf8569', 'name' => 'Portfolio 1'), array('id' => '480af662-eb8c-47d3-886b-230540cf8569', 'name' => 'Portfolio 2'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CounterCachePostFixture.php0000644000000000000000000000261513365153155025157 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CounterCachePostFixture extends CakeTestFixture { public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => 255), 'user_id' => array('type' => 'integer', 'null' => true), 'published' => array('type' => 'boolean', 'null' => false, 'default' => 0) ); public $records = array( array('id' => 1, 'title' => 'Rock and Roll', 'user_id' => 66, 'published' => false), array('id' => 2, 'title' => 'Music', 'user_id' => 66, 'published' => true), array('id' => 3, 'title' => 'Food', 'user_id' => 301, 'published' => true), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/StoryFixture.php0000644000000000000000000000223013365153155023057 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * StoryFixture * * @package Cake.Test.Fixture */ class StoryFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'story' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('title' => 'First Story'), array('title' => 'Second Story') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArticleFeaturedFixture.php0000644000000000000000000000352013365153155025005 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArticleFeaturedFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user_id' => array('type' => 'integer', 'null' => false), 'title' => array('type' => 'string', 'null' => false), 'body' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CampaignFixture.php0000644000000000000000000000216513365153155023465 0ustar rootroot array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false), ); /** * records property * * @var array */ public $records = array( array('name' => 'Hurtigruten'), array('name' => 'Colorline'), array('name' => 'Queen of Scandinavia') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArticleFeaturedsTagsFixture.php0000644000000000000000000000224713365153155026014 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArticleFeaturedsTagsFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'article_featured_id' => array('type' => 'integer', 'null' => false), 'tag_id' => array('type' => 'integer', 'null' => false), 'indexes' => array('UNIQUE_FEATURED' => array('column' => array('article_featured_id', 'tag_id'), 'unique' => 1)) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TestPluginCommentFixture.php0000644000000000000000000000477513365153155025400 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 7660 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TestPluginCommentFixture * * @package Cake.Test.Fixture */ class TestPluginCommentFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'article_id' => array('type' => 'integer', 'null' => false), 'user_id' => array('type' => 'integer', 'null' => false), 'comment' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Plugin Article', 'published' => 'Y', 'created' => '2008-09-24 10:45:23', 'updated' => '2008-09-24 10:47:31'), array('id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Plugin Article', 'published' => 'Y', 'created' => '2008-09-24 10:47:23', 'updated' => '2008-09-24 10:49:31'), array('id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Plugin Article', 'published' => 'Y', 'created' => '2008-09-24 10:49:23', 'updated' => '2008-09-24 10:51:31'), array('id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Plugin Article', 'published' => 'N', 'created' => '2008-09-24 10:51:23', 'updated' => '2008-09-24 10:53:31'), array('id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Plugin Article', 'published' => 'Y', 'created' => '2008-09-24 10:53:23', 'updated' => '2008-09-24 10:55:31'), array('id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Plugin Article', 'published' => 'Y', 'created' => '2008-09-24 10:55:23', 'updated' => '2008-09-24 10:57:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DomainFixture.php0000644000000000000000000000362513365153155023157 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DomainFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'domain' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('domain' => 'cakephp.org', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('domain' => 'book.cakephp.org', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('domain' => 'api.cakephp.org', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('domain' => 'mark-story.com', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('domain' => 'tinadurocher.com', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('domain' => 'chavik.com', 'created' => '2001-02-03 00:01:02', 'updated' => '2007-03-17 01:22:31'), array('domain' => 'xintesa.com', 'created' => '2001-02-03 00:01:02', 'updated' => '2007-03-17 01:22:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/FruitsUuidTagFixture.php0000644000000000000000000000264413365153155024507 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7953 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * FruitsUuidTagFixture * * @package Cake.Test.Fixture */ class FruitsUuidTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'fruit_id' => array('type' => 'string', 'null' => false, 'length' => 36, 'key' => 'primary'), 'uuid_tag_id' => array('type' => 'string', 'null' => false, 'length' => 36, 'key' => 'primary'), 'indexes' => array( 'unique_fruits_tags' => array('unique' => true, 'column' => array('fruit_id', 'uuid_tag_id')), ), ); /** * records property * * @var array */ public $records = array( array('fruit_id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'uuid_tag_id' => '481fc6d0-b920-43e0-e50f-6d1740cf8569') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DependencyFixture.php0000644000000000000000000000226713365153155024027 0ustar rootroot 'integer', 'child_id' => 'integer', 'parent_id' => 'integer' ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'child_id' => 1, 'parent_id' => 2), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/PrefixTestFixture.php0000644000000000000000000000172413365153155024043 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * PrefixTestFixture * * @package Cake.Test.Fixture */ class PrefixTestFixture extends CakeTestFixture { public $table = 'prefix_prefix_tests'; public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AuthorFixture.php0000644000000000000000000000346013365153155023207 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AuthorFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user' => array('type' => 'string', 'default' => null), 'password' => array('type' => 'string', 'default' => null), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31'), array('user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/StoriesTagFixture.php0000644000000000000000000000235013365153155024026 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * StoriesTagFixture * * @package Cake.Test.Fixture */ class StoriesTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'story' => array('type' => 'integer', 'null' => false), 'tag_id' => array('type' => 'integer', 'null' => false), 'indexes' => array('UNIQUE_STORY_TAG' => array('column' => array('story', 'tag_id'), 'unique' => 1)) ); /** * records property * * @var array */ public $records = array( array('story' => 1, 'tag_id' => 1) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/MyUserFixture.php0000644000000000000000000000225513365153155023172 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * MyUserFixture * * @package Cake.Test.Fixture */ class MyUserFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'firstname' => array('type' => 'string', 'null' => false), ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'firstname' => 'userA'), array('id' => 2, 'firstname' => 'userB') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CommentFixture.php0000644000000000000000000000462113365153155023347 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CommentFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'article_id' => array('type' => 'integer', 'null' => false), 'user_id' => array('type' => 'integer', 'null' => false), 'comment' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31'), array('article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31'), array('article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31'), array('article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31'), array('article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31'), array('article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/JoinBFixture.php0000644000000000000000000000277213365153155022753 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6317 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * JoinBFixture * * @package Cake.Test.Fixture */ class JoinBFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'default' => ''), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('name' => 'Join B 1', 'created' => '2008-01-03 10:55:01', 'updated' => '2008-01-03 10:55:01'), array('name' => 'Join B 2', 'created' => '2008-01-03 10:55:02', 'updated' => '2008-01-03 10:55:02'), array('name' => 'Join B 3', 'created' => '2008-01-03 10:55:03', 'updated' => '2008-01-03 10:55:03') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UnderscoreFieldFixture.php0000644000000000000000000000343313365153155025022 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UnderscoreFieldFixture class * * @package Cake.Test.Fixture */ class UnderscoreFieldFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user_id' => array('type' => 'integer', 'null' => false), 'my_model_has_a_field' => array('type' => 'string', 'null' => false), 'body_field' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'another_field' => array('type' => 'integer', 'length' => 3), ); /** * records property * * @var array */ public $records = array( array('user_id' => 1, 'my_model_has_a_field' => 'First Article', 'body_field' => 'First Article Body', 'published' => 'Y', 'another_field' => 2), array('user_id' => 3, 'my_model_has_a_field' => 'Second Article', 'body_field' => 'Second Article Body', 'published' => 'Y', 'another_field' => 3), array('user_id' => 1, 'my_model_has_a_field' => 'Third Article', 'body_field' => 'Third Article Body', 'published' => 'Y', 'another_field' => 5), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AcoFixture.php0000644000000000000000000000545313365153155022453 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AcoFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'model' => array('type' => 'string', 'null' => true), 'foreign_key' => array('type' => 'integer', 'length' => 10, 'null' => true), 'alias' => array('type' => 'string', 'default' => ''), 'lft' => array('type' => 'integer', 'length' => 10, 'null' => true), 'rght' => array('type' => 'integer', 'length' => 10, 'null' => true) ); /** * records property * * @var array */ public $records = array( array('parent_id' => null, 'model' => null, 'foreign_key' => null, 'alias' => 'ROOT', 'lft' => 1, 'rght' => 24), array('parent_id' => 1, 'model' => null, 'foreign_key' => null, 'alias' => 'Controller1', 'lft' => 2, 'rght' => 9), array('parent_id' => 2, 'model' => null, 'foreign_key' => null, 'alias' => 'action1', 'lft' => 3, 'rght' => 6), array('parent_id' => 3, 'model' => null, 'foreign_key' => null, 'alias' => 'record1', 'lft' => 4, 'rght' => 5), array('parent_id' => 2, 'model' => null, 'foreign_key' => null, 'alias' => 'action2', 'lft' => 7, 'rght' => 8), array('parent_id' => 1, 'model' => null, 'foreign_key' => null, 'alias' => 'Controller2', 'lft' => 10, 'rght' => 17), array('parent_id' => 6, 'model' => null, 'foreign_key' => null, 'alias' => 'action1', 'lft' => 11, 'rght' => 14), array('parent_id' => 7, 'model' => null, 'foreign_key' => null, 'alias' => 'record1', 'lft' => 12, 'rght' => 13), array('parent_id' => 6, 'model' => null, 'foreign_key' => null, 'alias' => 'action2', 'lft' => 15, 'rght' => 16), array('parent_id' => 1, 'model' => null, 'foreign_key' => null, 'alias' => 'Users', 'lft' => 18, 'rght' => 23), array('parent_id' => 9, 'model' => null, 'foreign_key' => null, 'alias' => 'Users', 'lft' => 19, 'rght' => 22), array('parent_id' => 10, 'model' => null, 'foreign_key' => null, 'alias' => 'view', 'lft' => 20, 'rght' => 21), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AccountFixture.php0000644000000000000000000000245613365153155023345 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AccountFixture extends CakeTestFixture { public $table = 'Accounts'; /** * fields property * * @var array */ public $fields = array( 'iAccountId' => array('type' => 'integer', 'key' => 'primary'), 'cDescription' => array('type' => 'string', 'length' => 10, 'null' => true) ); /** * records property * * @var array */ public $records = array( array('cDescription' => 'gwoo'), array('cDescription' => 'phpnut'), array('cDescription' => 'schreck'), array('cDescription' => 'dude') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArosAcoFixture.php0000644000000000000000000000273613365153155023301 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArosAcoFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'aro_id' => array('type' => 'integer', 'length' => 10, 'null' => false), 'aco_id' => array('type' => 'integer', 'length' => 10, 'null' => false), '_create' => array('type' => 'string', 'length' => 2, 'default' => 0), '_read' => array('type' => 'string', 'length' => 2, 'default' => 0), '_update' => array('type' => 'string', 'length' => 2, 'default' => 0), '_delete' => array('type' => 'string', 'length' => 2, 'default' => 0) ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/JoinThingFixture.php0000644000000000000000000000343413365153155023637 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * JoinThingFixture * * @package Cake.Test.Fixture */ class JoinThingFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'something_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'something_else_id' => array('type' => 'integer', 'default' => null), 'doomed' => array('type' => 'boolean', 'default' => '0'), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('something_id' => 1, 'something_else_id' => 2, 'doomed' => '1', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('something_id' => 2, 'something_else_id' => 3, 'doomed' => '0', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('something_id' => 3, 'something_else_id' => 1, 'doomed' => '1', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ThreadFixture.php0000644000000000000000000000250513365153155023153 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ThreadFixture * * @package Cake.Test.Fixture */ class ThreadFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'project_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('project_id' => 1, 'name' => 'Project 1, Thread 1'), array('project_id' => 1, 'name' => 'Project 1, Thread 2'), array('project_id' => 2, 'name' => 'Project 2, Thread 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidFixture.php0000644000000000000000000000353613365153155022657 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6700 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidFixture. * * @package Cake.Test.Fixture */ class UuidFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 36, 'key' => 'primary'), 'title' => 'string', 'count' => array('type' => 'integer', 'default' => 0), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('id' => '47c36f9c-bc00-4d17-9626-4e183ca6822b', 'title' => 'Unique record 1', 'count' => 2, 'created' => '2008-03-13 01:16:23', 'updated' => '2008-03-13 01:18:31'), array('id' => '47c36f9c-f2b0-43f5-b3f7-4e183ca6822b', 'title' => 'Unique record 2', 'count' => 4, 'created' => '2008-03-13 01:18:24', 'updated' => '2008-03-13 01:20:32'), array('id' => '47c36f9c-0ffc-4084-9b03-4e183ca6822b', 'title' => 'Unique record 3', 'count' => 5, 'created' => '2008-03-13 01:20:25', 'updated' => '2008-03-13 01:22:33'), array('id' => '47c36f9c-2578-4c2e-aeab-4e183ca6822b', 'title' => 'Unique record 4', 'count' => 3, 'created' => '2008-03-13 01:22:26', 'updated' => '2008-03-13 01:24:34'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BiddingMessageFixture.php0000644000000000000000000000251213365153155024607 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.3.14 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BiddingMessageFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'bidding' => array('type' => 'string', 'null' => false, 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('bidding' => 'One', 'name' => 'Message 1'), array('bidding' => 'Two', 'name' => 'Message 2'), array('bidding' => 'Three', 'name' => 'Message 3'), array('bidding' => 'Four', 'name' => 'Message 4') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ExteriorTypeCategoryFixture.php0000644000000000000000000000234713365153155026111 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ExteriorTypeCategoryFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'image_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('image_id' => 1, 'name' => 'ExteriorTypeCategory 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BakeArticlesBakeTagFixture.php0000644000000000000000000000233613365153155025516 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BakeArticlesBakeTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'bake_article_id' => array('type' => 'integer', 'null' => false), 'bake_tag_id' => array('type' => 'integer', 'null' => false), 'indexes' => array('UNIQUE_TAG' => array('column' => array('bake_article_id', 'bake_tag_id'), 'unique' => 1)) ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ThePaperMonkiesFixture.php0000644000000000000000000000220713365153155025001 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ThePaperMonkiesFixture * * @package Cake.Test.Fixture */ class ThePaperMonkiesFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'apple_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'device_id' => array('type' => 'integer', 'length' => 10, 'null' => true) ); /** * records property * * @var array */ public $records = array(); } ././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidnativeitemsUuidnativeportfolioNumericidFixture.phpZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidnativeitemsUuidnativeportfolioNumericidFixture.p0000644000000000000000000000345413365153155032433 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidnativeitemsUuidnativeportfolioNumericidFixture * * @package Cake.Test.Fixture */ class UuidnativeitemsUuidnativeportfolioNumericidFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'length' => 10, 'key' => 'primary'), 'uuidnativeitem_id' => array('type' => 'uuid', 'null' => false), 'uuidnativeportfolio_id' => array('type' => 'uuid', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('uuidnativeitem_id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'uuidnativeportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('uuidnativeitem_id' => '48298a29-81c0-4c26-a7fb-413140cf8569', 'uuidnativeportfolio_id' => '480af662-eb8c-47d3-886b-230540cf8569'), array('uuidnativeitem_id' => '482b7756-8da0-419a-b21f-27da40cf8569', 'uuidnativeportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('uuidnativeitem_id' => '482cfd4b-0e7c-4ea3-9582-4cec40cf8569', 'uuidnativeportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AcoActionFixture.php0000644000000000000000000000270513365153155023606 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AcoActionFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'model' => array('type' => 'string', 'default' => ''), 'foreign_key' => array('type' => 'integer', 'length' => 10, 'null' => true), 'alias' => array('type' => 'string', 'default' => ''), 'lft' => array('type' => 'integer', 'length' => 10, 'null' => true), 'rght' => array('type' => 'integer', 'length' => 10, 'null' => true) ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/JoinACFixture.php0000644000000000000000000000357213365153155023054 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6317 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * JoinACFixture * * @package Cake.Test.Fixture */ class JoinACFixture extends CakeTestFixture { /** * name property * * @var string */ public $name = 'JoinAsJoinC'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'join_a_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'join_c_id' => array('type' => 'integer', 'default' => null), 'other' => array('type' => 'string', 'default' => ''), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('join_a_id' => 1, 'join_c_id' => 2, 'other' => 'Data for Join A 1 Join C 2', 'created' => '2008-01-03 10:57:22', 'updated' => '2008-01-03 10:57:22'), array('join_a_id' => 2, 'join_c_id' => 3, 'other' => 'Data for Join A 2 Join C 3', 'created' => '2008-01-03 10:57:23', 'updated' => '2008-01-03 10:57:23'), array('join_a_id' => 3, 'join_c_id' => 1, 'other' => 'Data for Join A 3 Join C 1', 'created' => '2008-01-03 10:57:24', 'updated' => '2008-01-03 10:57:24') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/InnoFixture.php0000644000000000000000000000227513365153155022653 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * InnoFixture * * @package Cake.Test.Fixture */ class InnoFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => true), 'tableParameters' => array( 'engine' => 'InnoDB' ) ); /** * records property * * @var array */ public $records = array( array('name' => 'Name 1'), array('name' => 'Name 2'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ContentFixture.php0000644000000000000000000000251713365153155023361 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ContentFixture extends CakeTestFixture { public $table = 'Content'; /** * fields property * * @var array */ public $fields = array( 'iContentId' => array('type' => 'integer', 'key' => 'primary'), 'cDescription' => array('type' => 'string', 'length' => 50, 'null' => true) ); /** * records property * * @var array */ public $records = array( array('cDescription' => 'Test Content 1'), array('cDescription' => 'Test Content 2'), array('cDescription' => 'Test Content 3'), array('cDescription' => 'Test Content 4') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TestPluginArticleFixture.php0000644000000000000000000000356213365153155025352 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 7660 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TestPluginArticleFixture * * @package Cake.Test.Fixture */ class TestPluginArticleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user_id' => array('type' => 'integer', 'null' => false), 'title' => array('type' => 'string', 'null' => false), 'body' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('user_id' => 1, 'title' => 'First Plugin Article', 'body' => 'First Plugin Article Body', 'published' => 'Y', 'created' => '2008-09-24 10:39:23', 'updated' => '2008-09-24 10:41:31'), array('user_id' => 3, 'title' => 'Second Plugin Article', 'body' => 'Second Plugin Article Body', 'published' => 'Y', 'created' => '2008-09-24 10:41:23', 'updated' => '2008-09-24 10:43:31'), array('user_id' => 1, 'title' => 'Third Plugin Article', 'body' => 'Third Plugin Article Body', 'published' => 'Y', 'created' => '2008-09-24 10:43:23', 'updated' => '2008-09-24 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BakeCommentFixture.php0000644000000000000000000000251613365153155024133 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * BakeCommentFixture fixture for testing bake * * @package Cake.Test.Fixture */ class BakeCommentFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'otherid' => array('type' => 'integer', 'key' => 'primary'), 'bake_article_id' => array('type' => 'integer', 'null' => false), 'bake_user_id' => array('type' => 'integer', 'null' => false), 'comment' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array(); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DocumentFixture.php0000644000000000000000000000235113365153155023521 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DocumentFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'document_directory_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('document_directory_id' => 1, 'name' => 'Document 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UnconventionalTreeFixture.php0000644000000000000000000000247513365153155025574 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7879 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UnconventionalTreeFixture class * * Like Number tree, but doesn't use the default values for lft and rght or parent_id * * @uses CakeTestFixture * @package Cake.Test.Fixture */ class UnconventionalTreeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'join' => 'integer', 'left' => array('type' => 'integer', 'null' => false), 'right' => array('type' => 'integer', 'null' => false), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CacheTestModelFixture.php0000644000000000000000000000221413365153155024565 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CacheTestModelFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 255, 'key' => 'primary'), 'data' => array('type' => 'string', 'length' => 255, 'default' => ''), 'expires' => array('type' => 'integer', 'length' => 10, 'default' => '0'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AroFixture.php0000644000000000000000000000362713365153155022473 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AroFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'model' => array('type' => 'string', 'null' => true), 'foreign_key' => array('type' => 'integer', 'length' => 10, 'null' => true), 'alias' => array('type' => 'string', 'default' => ''), 'lft' => array('type' => 'integer', 'length' => 10, 'null' => true), 'rght' => array('type' => 'integer', 'length' => 10, 'null' => true) ); /** * records property * * @var array */ public $records = array( array('parent_id' => null, 'model' => null, 'foreign_key' => null, 'alias' => 'ROOT', 'lft' => 1, 'rght' => 8), array('parent_id' => '1', 'model' => 'Group', 'foreign_key' => '1', 'alias' => 'admins', 'lft' => 2, 'rght' => 7), array('parent_id' => '2', 'model' => 'AuthUser', 'foreign_key' => '1', 'alias' => 'Gandalf', 'lft' => 3, 'rght' => 4), array('parent_id' => '2', 'model' => 'AuthUser', 'foreign_key' => '2', 'alias' => 'Elrond', 'lft' => 5, 'rght' => 6) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/JoinABFixture.php0000644000000000000000000000357213365153155023053 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6317 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * JoinABFixture * * @package Cake.Test.Fixture */ class JoinABFixture extends CakeTestFixture { /** * name property * * @var string */ public $name = 'JoinAsJoinB'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'join_a_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'join_b_id' => array('type' => 'integer', 'default' => null), 'other' => array('type' => 'string', 'default' => ''), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('join_a_id' => 1, 'join_b_id' => 2, 'other' => 'Data for Join A 1 Join B 2', 'created' => '2008-01-03 10:56:33', 'updated' => '2008-01-03 10:56:33'), array('join_a_id' => 2, 'join_b_id' => 3, 'other' => 'Data for Join A 2 Join B 3', 'created' => '2008-01-03 10:56:34', 'updated' => '2008-01-03 10:56:34'), array('join_a_id' => 3, 'join_b_id' => 1, 'other' => 'Data for Join A 3 Join B 1', 'created' => '2008-01-03 10:56:35', 'updated' => '2008-01-03 10:56:35') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CounterCachePostNonstandardPrimaryKeyFixture.php0000644000000000000000000000243213365153155031365 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CounterCachePostNonstandardPrimaryKeyFixture extends CakeTestFixture { public $fields = array( 'pid' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => 255, 'null' => false), 'uid' => array('type' => 'integer', 'null' => true), ); public $records = array( array('pid' => 1, 'title' => 'Rock and Roll', 'uid' => 66), array('pid' => 2, 'title' => 'Music', 'uid' => 66), array('pid' => 3, 'title' => 'Food', 'uid' => 301), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/FlagTreeFixture.php0000644000000000000000000000257113365153155023440 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5331 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Flag Tree Test Fixture * * Like Number Tree, but uses a flag for testing scope parameters * * @package Cake.Test.Fixture */ class FlagTreeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'parent_id' => 'integer', 'lft' => array('type' => 'integer', 'null' => false), 'rght' => array('type' => 'integer', 'null' => false), 'flag' => array('type' => 'integer', 'null' => false, 'length' => 1, 'default' => 0) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DomainsSiteFixture.php0000644000000000000000000000410513365153155024161 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DomainsSiteFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'domain_id' => array('type' => 'integer', 'null' => false), 'site_id' => array('type' => 'integer', 'null' => false), 'active' => array('type' => 'boolean', 'null' => false, 'default' => false), 'created' => 'datetime', 'updated' => 'datetime', ); /** * records property * * @var array */ public $records = array( array('site_id' => 1, 'domain_id' => 1, 'active' => true, 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('site_id' => 1, 'domain_id' => 2, 'active' => true, 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('site_id' => 2, 'domain_id' => 4, 'active' => true, 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('site_id' => 2, 'domain_id' => 5, 'active' => true, 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('site_id' => 3, 'domain_id' => 6, 'active' => true, 'created' => '2001-02-03 00:01:02', 'updated' => '2007-03-17 01:22:31'), array('site_id' => 3, 'domain_id' => 7, 'active' => false, 'created' => '2001-02-03 00:01:02', 'updated' => '2007-03-17 01:22:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArticlesTagFixture.php0000644000000000000000000000257713365153155024157 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArticlesTagFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'article_id' => array('type' => 'integer', 'null' => false), 'tag_id' => array('type' => 'integer', 'null' => false), 'indexes' => array('UNIQUE_TAG2' => array('column' => array('article_id', 'tag_id'), 'unique' => 1)) ); /** * records property * * @var array */ public $records = array( array('article_id' => 1, 'tag_id' => 1), array('article_id' => 1, 'tag_id' => 2), array('article_id' => 2, 'tag_id' => 1), array('article_id' => 2, 'tag_id' => 3) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/PortfolioFixture.php0000644000000000000000000000245713365153155023727 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * PortfolioFixture * * @package Cake.Test.Fixture */ class PortfolioFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'seller_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('seller_id' => 1, 'name' => 'Portfolio 1'), array('seller_id' => 1, 'name' => 'Portfolio 2'), array('seller_id' => 2, 'name' => 'Portfolio 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AuthUserFixture.php0000644000000000000000000000374013365153155023506 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AuthUserFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'username' => array('type' => 'string', 'null' => false), 'password' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('username' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('username' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('username' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31'), array('username' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), array('username' => 'chartjes', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/GuildFixture.php0000644000000000000000000000224313365153155023007 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 2.1 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * GuildFixture * * @package Cake.Test.Fixture */ class GuildFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), ); /** * records property * * @var array */ public $records = array( array('name' => 'Warriors'), array('name' => 'Rangers'), array('name' => 'Wizards'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DataTestFixture.php0000644000000000000000000000262513365153155023460 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6700 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DataTestFixture extends CakeTestFixture { /** * Fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'count' => array('type' => 'integer', 'default' => 0), 'float' => array('type' => 'float', 'default' => 0), 'created' => array('type' => 'datetime', 'default' => null), 'updated' => array('type' => 'datetime', 'default' => null) ); /** * Records property * * @var array */ public $records = array( array( 'count' => 2, 'float' => 2.4, 'created' => '2010-09-06 12:28:00', 'updated' => '2010-09-06 12:28:00' ) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CounterCacheUserNonstandardPrimaryKeyFixture.php0000644000000000000000000000236313365153155031361 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CounterCacheUserNonstandardPrimaryKeyFixture extends CakeTestFixture { public $fields = array( 'uid' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false), 'post_count' => array('type' => 'integer', 'null' => true) ); public $records = array( array('uid' => 66, 'name' => 'Alexander', 'post_count' => 2), array('uid' => 301, 'name' => 'Steven', 'post_count' => 1), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/JoinCFixture.php0000644000000000000000000000277213365153155022754 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.6317 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * JoinCFixture * * @package Cake.Test.Fixture */ class JoinCFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'default' => ''), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('name' => 'Join C 1', 'created' => '2008-01-03 10:56:11', 'updated' => '2008-01-03 10:56:11'), array('name' => 'Join C 2', 'created' => '2008-01-03 10:56:12', 'updated' => '2008-01-03 10:56:12'), array('name' => 'Join C 3', 'created' => '2008-01-03 10:56:13', 'updated' => '2008-01-03 10:56:13') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AroTwoFixture.php0000644000000000000000000000515413365153155023162 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AroTwoFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'model' => array('type' => 'string', 'null' => true), 'foreign_key' => array('type' => 'integer', 'length' => 10, 'null' => true), 'alias' => array('type' => 'string', 'default' => ''), 'lft' => array('type' => 'integer', 'length' => 10, 'null' => true), 'rght' => array('type' => 'integer', 'length' => 10, 'null' => true) ); /** * records property * * @var array */ public $records = array( array('parent_id' => null, 'model' => null, 'foreign_key' => null, 'alias' => 'root', 'lft' => '1', 'rght' => '20'), array('parent_id' => 1, 'model' => 'Group', 'foreign_key' => '1', 'alias' => 'admin', 'lft' => '2', 'rght' => '5'), array('parent_id' => 1, 'model' => 'Group', 'foreign_key' => '2', 'alias' => 'managers', 'lft' => '6', 'rght' => '9'), array('parent_id' => 1, 'model' => 'Group', 'foreign_key' => '3', 'alias' => 'users', 'lft' => '10', 'rght' => '19'), array('parent_id' => 2, 'model' => 'User', 'foreign_key' => '1', 'alias' => 'Bobs', 'lft' => '3', 'rght' => '4'), array('parent_id' => 3, 'model' => 'User', 'foreign_key' => '2', 'alias' => 'Lumbergh', 'lft' => '7', 'rght' => '8'), array('parent_id' => 4, 'model' => 'User', 'foreign_key' => '3', 'alias' => 'Samir', 'lft' => '11', 'rght' => '12'), array('parent_id' => 4, 'model' => 'User', 'foreign_key' => '4', 'alias' => 'Micheal', 'lft' => '13', 'rght' => '14'), array('parent_id' => 4, 'model' => 'User', 'foreign_key' => '5', 'alias' => 'Peter', 'lft' => '15', 'rght' => '16'), array('parent_id' => 4, 'model' => 'User', 'foreign_key' => '6', 'alias' => 'Milton', 'lft' => '17', 'rght' => '18'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/TranslateWithPrefixFixture.php0000644000000000000000000000752513365153155025722 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5669 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * TranslateWithPrefixFixture * * @package Cake.Test.Fixture */ class TranslateWithPrefixFixture extends CakeTestFixture { /** * table property * * @var string */ public $table = 'i18n_translate_with_prefixes'; /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'locale' => array('type' => 'string', 'length' => 6, 'null' => false), 'model' => array('type' => 'string', 'null' => false), 'foreign_key' => array('type' => 'integer', 'null' => false), 'field' => array('type' => 'string', 'null' => false), 'content' => array('type' => 'text') ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1'), array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Obsah #1'), array('id' => 7, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title #2'), array('id' => 8, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Content #2'), array('id' => 9, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Titel #2'), array('id' => 10, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Inhalt #2'), array('id' => 11, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Titulek #2'), array('id' => 12, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Obsah #2'), array('id' => 13, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title #3'), array('id' => 14, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Content #3'), array('id' => 15, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Titel #3'), array('id' => 16, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Inhalt #3'), array('id' => 17, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Titulek #3'), array('id' => 18, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Obsah #3') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AuthUserCustomFieldFixture.php0000644000000000000000000000402513365153155025642 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.1.8013 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class AuthUserCustomFieldFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'email' => array('type' => 'string', 'null' => false), 'password' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('email' => 'mariano@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), array('email' => 'nate@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31'), array('email' => 'larry@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31'), array('email' => 'garrett@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), array('email' => 'chartjes@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DeviceTypeCategoryFixture.php0000644000000000000000000000222513365153155025502 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DeviceTypeCategoryFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('name' => 'DeviceTypeCategory 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/FruitFixture.php0000644000000000000000000000265013365153155023036 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7953 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Fruit Fixtures * * @package Cake.Test.Fixture */ class FruitFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'string', 'length' => 36, 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255), 'color' => array('type' => 'string', 'length' => 13), 'shape' => array('type' => 'string', 'length' => 255), 'taste' => array('type' => 'string', 'length' => 255) ); /** * records property * * @var array */ public $records = array( array( 'id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'name' => 'Orange', 'color' => 'orange', 'shape' => 'Spherical', 'taste' => 'Tangy & Sweet' ) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/MyCategoriesMyProductsFixture.php0000644000000000000000000000247213365153155026374 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * MyCategoriesMyProductsFixture * * @package Cake.Test.Fixture */ class MyCategoriesMyProductsFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'my_category_id' => array('type' => 'integer'), 'my_product_id' => array('type' => 'integer'), ); /** * records property * * @var array */ public $records = array( array('my_category_id' => 1, 'my_product_id' => 1), array('my_category_id' => 2, 'my_product_id' => 1), array('my_category_id' => 2, 'my_product_id' => 2), array('my_category_id' => 3, 'my_product_id' => 2), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/MyProductFixture.php0000644000000000000000000000224713365153155023675 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * MyProductFixture * * @package Cake.Test.Fixture */ class MyProductFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'name' => 'book'), array('id' => 2, 'name' => 'computer'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/FilmFileFixture.php0000644000000000000000000000223513365153155023433 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * FilmFileFixture * * @package Cake.Test.Fixture */ class FilmFileFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255) ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'name' => 'one'), array('id' => 2, 'name' => 'two') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/DeviceTypeFixture.php0000644000000000000000000000336113365153155024006 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class DeviceTypeFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'device_type_category_id' => array('type' => 'integer', 'null' => false), 'feature_set_id' => array('type' => 'integer', 'null' => false), 'exterior_type_category_id' => array('type' => 'integer', 'null' => false), 'image_id' => array('type' => 'integer', 'null' => false), 'extra1_id' => array('type' => 'integer', 'null' => false), 'extra2_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false), 'order' => array('type' => 'integer', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('device_type_category_id' => 1, 'feature_set_id' => 1, 'exterior_type_category_id' => 1, 'image_id' => 1, 'extra1_id' => 1, 'extra2_id' => 1, 'name' => 'DeviceType 1', 'order' => 0) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidnativeitemFixture.php0000644000000000000000000000332413365153155024740 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuidnativeitemFixture * * @package Cake.Test.Fixture */ class UuidnativeitemFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'uuid', 'key' => 'primary'), 'published' => array('type' => 'boolean', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'published' => 0, 'name' => 'Item 1'), array('id' => '48298a29-81c0-4c26-a7fb-413140cf8569', 'published' => 0, 'name' => 'Item 2'), array('id' => '482b7756-8da0-419a-b21f-27da40cf8569', 'published' => 0, 'name' => 'Item 3'), array('id' => '482cfd4b-0e7c-4ea3-9582-4cec40cf8569', 'published' => 0, 'name' => 'Item 4'), array('id' => '4831181b-4020-4983-a29b-131440cf8569', 'published' => 0, 'name' => 'Item 5'), array('id' => '483798c8-c7cc-430e-8cf9-4fcc40cf8569', 'published' => 0, 'name' => 'Item 6') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AdFixture.php0000644000000000000000000000350213365153155022266 0ustar rootroot array('type' => 'integer', 'key' => 'primary'), 'campaign_id' => array('type' => 'integer'), 'parent_id' => array('type' => 'integer'), 'lft' => array('type' => 'integer'), 'rght' => array('type' => 'integer'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false) ); /** * records property * * @var array */ public $records = array( array('parent_id' => null, 'lft' => 1, 'rght' => 2, 'campaign_id' => 1, 'name' => 'Nordover'), array('parent_id' => null, 'lft' => 3, 'rght' => 4, 'campaign_id' => 1, 'name' => 'Statbergen'), array('parent_id' => null, 'lft' => 5, 'rght' => 6, 'campaign_id' => 1, 'name' => 'Feroy'), array('parent_id' => null, 'lft' => 7, 'rght' => 12, 'campaign_id' => 2, 'name' => 'Newcastle'), array('parent_id' => null, 'lft' => 8, 'rght' => 9, 'campaign_id' => 2, 'name' => 'Dublin'), array('parent_id' => null, 'lft' => 10, 'rght' => 11, 'campaign_id' => 2, 'name' => 'Alborg'), array('parent_id' => null, 'lft' => 13, 'rght' => 14, 'campaign_id' => 3, 'name' => 'New York') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/MessageFixture.php0000644000000000000000000000250313365153155023326 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * MessageFixture * * @package Cake.Test.Fixture */ class MessageFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'thread_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('thread_id' => 1, 'name' => 'Thread 1, Message 1'), array('thread_id' => 2, 'name' => 'Thread 2, Message 1'), array('thread_id' => 3, 'name' => 'Thread 3, Message 1') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuiditemsUuidportfolioNumericidFixture.php0000644000000000000000000000337413365153155030346 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuiditemsUuidportfolioNumericidFixture * * @package Cake.Test.Fixture */ class UuiditemsUuidportfolioNumericidFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'length' => 10, 'key' => 'primary'), 'uuiditem_id' => array('type' => 'string', 'length' => 36, 'null' => false), 'uuidportfolio_id' => array('type' => 'string', 'length' => 36, 'null' => false) ); /** * records property * * @var array */ public $records = array( array('uuiditem_id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'uuidportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('uuiditem_id' => '48298a29-81c0-4c26-a7fb-413140cf8569', 'uuidportfolio_id' => '480af662-eb8c-47d3-886b-230540cf8569'), array('uuiditem_id' => '482b7756-8da0-419a-b21f-27da40cf8569', 'uuidportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('uuiditem_id' => '482cfd4b-0e7c-4ea3-9582-4cec40cf8569', 'uuidportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/CategoryFixture.php0000644000000000000000000000427313365153155023525 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class CategoryFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'parent_id' => array('type' => 'integer', 'null' => false), 'name' => array('type' => 'string', 'null' => false), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('parent_id' => 0, 'name' => 'Category 1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 1, 'name' => 'Category 1.1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 1, 'name' => 'Category 1.2', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 0, 'name' => 'Category 2', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 0, 'name' => 'Category 3', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 5, 'name' => 'Category 3.1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 2, 'name' => 'Category 1.1.1', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), array('parent_id' => 2, 'name' => 'Category 1.1.2', 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/UuidnativeitemsUuidnativeportfolioFixture.php0000644000000000000000000000367313365153155031126 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * UuiditemsUuidportfolioFixture * * @package Cake.Test.Fixture */ class UuidnativeitemsUuidnativeportfolioFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'uuid', 'key' => 'primary'), 'uuidnativeitem_id' => array('type' => 'uuid', 'null' => false), 'uuidnativeportfolio_id' => array('type' => 'uuid', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('id' => '4850fd8f-cc5c-449f-bf34-0c5240cf8569', 'uuidnativeitem_id' => '481fc6d0-b920-43e0-a40d-6d1740cf8569', 'uuidnativeportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('id' => '4850fee5-d24c-4ea0-9759-0c2e40cf8569', 'uuidnativeitem_id' => '48298a29-81c0-4c26-a7fb-413140cf8569', 'uuidnativeportfolio_id' => '480af662-eb8c-47d3-886b-230540cf8569'), array('id' => '4851af6e-fa18-403d-b57e-437d40cf8569', 'uuidnativeitem_id' => '482b7756-8da0-419a-b21f-27da40cf8569', 'uuidnativeportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569'), array('id' => '4851b94c-9790-42dc-b760-4f9240cf8569', 'uuidnativeitem_id' => '482cfd4b-0e7c-4ea3-9582-4cec40cf8569', 'uuidnativeportfolio_id' => '4806e091-6940-4d2b-b227-303740cf8569') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/NumberTreeTwoFixture.php0000644000000000000000000000252613365153155024511 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.5331 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * NumberTreeTwoFixture * * Generates a tree of data for use testing the tree behavior * * @package Cake.Test.Fixture */ class NumberTreeTwoFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), 'number_tree_id' => array('type' => 'integer', 'null' => false), 'parent_id' => 'integer', 'lft' => array('type' => 'integer', 'null' => false), 'rght' => array('type' => 'integer', 'null' => false) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/MyCategoryFixture.php0000644000000000000000000000230213365153155024022 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * MyCategoryFixture * * @package Cake.Test.Fixture */ class MyCategoryFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'null' => false), ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'name' => 'A'), array('id' => 2, 'name' => 'B'), array('id' => 3, 'name' => 'C'), ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/SomethingElseFixture.php0000644000000000000000000000341313365153155024511 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * SomethingElseFixture * * @package Cake.Test.Fixture */ class SomethingElseFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'default' => ''), 'body' => array('type' => 'text'), 'published' => array('type' => 'string', 'default' => ''), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('title' => 'Second Post', 'body' => 'Second Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('title' => 'Third Post', 'body' => 'Third Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/AssertTagsTestCase.php0000644000000000000000000000471613365153155024117 0ustar rootrootMy link'; $pattern = array( 'a' => array('href' => '/test.html', 'class' => 'active'), 'My link', '/a' ); $this->assertTags($input, $pattern); $input = "My link"; $pattern = array( 'a' => array('href' => '/test.html', 'class' => 'active'), 'My link', '/a' ); $this->assertTags($input, $pattern); $input = "My link"; $pattern = array( 'a' => array('href' => 'preg:/.*\.html/', 'class' => 'active'), 'My link', '/a' ); $this->assertTags($input, $pattern); } /** * testNumericValuesInExpectationForAssertTags * * @return void */ public function testNumericValuesInExpectationForAssertTags() { $value = 220985; $input = '

' . $value . '

'; $pattern = array( 'assertTags($input, $pattern); $input = '

' . $value . '

' . $value . '

'; $pattern = array( 'assertTags($input, $pattern); $input = '

' . $value . '

' . $value . '

'; $pattern = array( ' array('id' => $value), 'assertTags($input, $pattern); } /** * testBadAssertTags * * @return void */ public function testBadAssertTags() { $input = 'My link'; $pattern = array( 'a' => array('hRef' => '/test.html', 'clAss' => 'active'), 'My link2', '/a' ); $this->assertTags($input, $pattern); } /** * testBadAssertTags * * @return void */ public function testBadAssertTags2() { $input = 'My link'; $pattern = array( ' array('href' => '/test.html', 'class' => 'active'), 'My link', '/a' ); $this->assertTags($input, $pattern); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/BookFixture.php0000644000000000000000000000265113365153155022640 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.7198 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class BookFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'isbn' => array('type' => 'string', 'length' => 13), 'title' => array('type' => 'string', 'length' => 255), 'author' => array('type' => 'string', 'length' => 255), 'year' => array('type' => 'integer', 'null' => true), 'pages' => array('type' => 'integer', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('id' => 1, 'isbn' => '1234567890', 'title' => 'Faust', 'author' => 'Johann Wolfgang von Goethe') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ArticleFixture.php0000644000000000000000000000350713365153155023332 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * Short description for class. * * @package Cake.Test.Fixture */ class ArticleFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'user_id' => array('type' => 'integer', 'null' => true), 'title' => array('type' => 'string', 'null' => true), 'body' => 'text', 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), 'created' => 'datetime', 'updated' => 'datetime' ); /** * records property * * @var array */ public $records = array( array('user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/ItemsPortfolioFixture.php0000644000000000000000000000267013365153155024726 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * ItemsPortfolioFixture * * @package Cake.Test.Fixture */ class ItemsPortfolioFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'item_id' => array('type' => 'integer', 'null' => false), 'portfolio_id' => array('type' => 'integer', 'null' => false) ); /** * records property * * @var array */ public $records = array( array('item_id' => 1, 'portfolio_id' => 1), array('item_id' => 2, 'portfolio_id' => 2), array('item_id' => 3, 'portfolio_id' => 1), array('item_id' => 4, 'portfolio_id' => 1), array('item_id' => 5, 'portfolio_id' => 1), array('item_id' => 6, 'portfolio_id' => 2) ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Fixture/SomethingFixture.php0000644000000000000000000000340313365153155023677 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Fixture * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ /** * SomethingFixture * * @package Cake.Test.Fixture */ class SomethingFixture extends CakeTestFixture { /** * fields property * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'default' => ''), 'body' => array('type' => 'text'), 'published' => array('type' => 'string', 'default' => ''), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) ); /** * records property * * @var array */ public $records = array( array('title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), array('title' => 'Second Post', 'body' => 'Second Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), array('title' => 'Third Post', 'body' => 'Third Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31') ); } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/0000755000000000000000000000000013365153155017107 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllCacheTest.php0000644000000000000000000000221613365153155022115 0ustar rootrootaddTestDirectory(CORE_TEST_CASES . DS . 'Cache'); $suite->addTestDirectory(CORE_TEST_CASES . DS . 'Cache' . DS . 'Engine'); return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Log/0000755000000000000000000000000013365153155017630 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Log/Engine/0000755000000000000000000000000013365153155021035 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Log/Engine/FileLogTest.php0000644000000000000000000001377013365153155023737 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Log.Engine * @since CakePHP(tm) v 1.3 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('FileLog', 'Log/Engine'); /** * CakeLogTest class * * @package Cake.Test.Case.Log.Engine */ class FileLogTest extends CakeTestCase { /** * testLogFileWriting method * * @return void */ public function testLogFileWriting() { $this->_deleteLogs(LOGS); $log = new FileLog(); $log->write('warning', 'Test warning'); $this->assertTrue(file_exists(LOGS . 'error.log')); $result = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning/', $result); $log->write('debug', 'Test warning'); $this->assertTrue(file_exists(LOGS . 'debug.log')); $result = file_get_contents(LOGS . 'debug.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: Test warning/', $result); $log->write('random', 'Test warning'); $this->assertTrue(file_exists(LOGS . 'random.log')); $result = file_get_contents(LOGS . 'random.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Random: Test warning/', $result); } /** * test using the path setting to write logs in other places. * * @return void */ public function testPathSetting() { $path = TMP . 'tests' . DS; $this->_deleteLogs($path); $log = new FileLog(compact('path')); $log->write('warning', 'Test warning'); $this->assertTrue(file_exists($path . 'error.log')); } /** * test log rotation * * @return void */ public function testRotation() { $path = TMP . 'tests' . DS; $this->_deleteLogs($path); file_put_contents($path . 'error.log', "this text is under 35 bytes\n"); $log = new FileLog(array( 'path' => $path, 'size' => 35, 'rotate' => 2 )); $log->write('warning', 'Test warning one'); $this->assertTrue(file_exists($path . 'error.log')); $result = file_get_contents($path . 'error.log'); $this->assertRegExp('/Warning: Test warning one/', $result); $this->assertEquals(0, count(glob($path . 'error.log.*'))); clearstatcache(); $log->write('warning', 'Test warning second'); $files = glob($path . 'error.log.*'); $this->assertEquals(1, count($files)); $result = file_get_contents($files[0]); $this->assertRegExp('/this text is under 35 bytes/', $result); $this->assertRegExp('/Warning: Test warning one/', $result); sleep(1); clearstatcache(); $log->write('warning', 'Test warning third'); $result = file_get_contents($path . 'error.log'); $this->assertRegExp('/Warning: Test warning third/', $result); $files = glob($path . 'error.log.*'); $this->assertEquals(2, count($files)); $result = file_get_contents($files[0]); $this->assertRegExp('/this text is under 35 bytes/', $result); $result = file_get_contents($files[1]); $this->assertRegExp('/Warning: Test warning second/', $result); file_put_contents($path . 'error.log.0000000000', "The oldest log file with over 35 bytes.\n"); sleep(1); clearstatcache(); $log->write('warning', 'Test warning fourth'); // rotate count reached so file count should not increase $files = glob($path . 'error.log.*'); $this->assertEquals(2, count($files)); $result = file_get_contents($path . 'error.log'); $this->assertRegExp('/Warning: Test warning fourth/', $result); $result = file_get_contents(array_pop($files)); $this->assertRegExp('/Warning: Test warning third/', $result); $result = file_get_contents(array_pop($files)); $this->assertRegExp('/Warning: Test warning second/', $result); file_put_contents($path . 'debug.log', "this text is just greater than 35 bytes\n"); $log = new FileLog(array( 'path' => $path, 'size' => 35, 'rotate' => 0 )); file_put_contents($path . 'debug.log.0000000000', "The oldest log file with over 35 bytes.\n"); $log->write('debug', 'Test debug'); $this->assertTrue(file_exists($path . 'debug.log')); $result = file_get_contents($path . 'debug.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: Test debug/', $result); $this->assertFalse(strstr($result, 'greater than 5 bytes')); $this->assertEquals(0, count(glob($path . 'debug.log.*'))); } public function testMaskSetting() { if (DS === '\\') { $this->markTestSkipped('File permission testing does not work on Windows.'); } $path = TMP . 'tests' . DS; $this->_deleteLogs($path); $log = new FileLog(array('path' => $path, 'mask' => 0666)); $log->write('warning', 'Test warning one'); $result = substr(sprintf('%o', fileperms($path . 'error.log')), -4); $expected = '0666'; $this->assertEquals($expected, $result); unlink($path . 'error.log'); $log = new FileLog(array('path' => $path, 'mask' => 0644)); $log->write('warning', 'Test warning two'); $result = substr(sprintf('%o', fileperms($path . 'error.log')), -4); $expected = '0644'; $this->assertEquals($expected, $result); unlink($path . 'error.log'); $log = new FileLog(array('path' => $path, 'mask' => 0640)); $log->write('warning', 'Test warning three'); $result = substr(sprintf('%o', fileperms($path . 'error.log')), -4); $expected = '0640'; $this->assertEquals($expected, $result); unlink($path . 'error.log'); } /** * helper function to clears all log files in specified directory * * @return void */ protected function _deleteLogs($dir) { $files = array_merge(glob($dir . '*.log'), glob($dir . '*.log.*')); foreach ($files as $file) { unlink($file); } } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Log/Engine/SyslogLogTest.php0000644000000000000000000000515213365153155024333 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Log.Engine * @since CakePHP(tm) v 2.4 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('SyslogLog', 'Log/Engine'); /** * SyslogLogTest class * * @package Cake.Test.Case.Log.Engine */ class SyslogLogTest extends CakeTestCase { /** * Tests that the connection to the logger is open with the right arguments * * @return void */ public function testOpenLog() { $log = $this->getMock('SyslogLog', array('_open', '_write')); $log->expects($this->once())->method('_open')->with('', LOG_ODELAY, LOG_USER); $log->write('debug', 'message'); $log = $this->getMock('SyslogLog', array('_open', '_write')); $log->config(array( 'prefix' => 'thing', 'flag' => LOG_NDELAY, 'facility' => LOG_MAIL, 'format' => '%s: %s' )); $log->expects($this->once())->method('_open') ->with('thing', LOG_NDELAY, LOG_MAIL); $log->write('debug', 'message'); } /** * Tests that single lines are written to syslog * * @dataProvider typesProvider * @return void */ public function testWriteOneLine($type, $expected) { $log = $this->getMock('SyslogLog', array('_open', '_write')); $log->expects($this->once())->method('_write')->with($expected, $type . ': Foo'); $log->write($type, 'Foo'); } /** * Tests that multiple lines are split and logged separately * * @return void */ public function testWriteMultiLine() { $log = $this->getMock('SyslogLog', array('_open', '_write')); $log->expects($this->at(1))->method('_write')->with(LOG_DEBUG, 'debug: Foo'); $log->expects($this->at(2))->method('_write')->with(LOG_DEBUG, 'debug: Bar'); $log->expects($this->exactly(2))->method('_write'); $log->write('debug', "Foo\nBar"); } /** * Data provider for the write function test * * @return array */ public function typesProvider() { return array( array('emergency', LOG_EMERG), array('alert', LOG_ALERT), array('critical', LOG_CRIT), array('error', LOG_ERR), array('warning', LOG_WARNING), array('notice', LOG_NOTICE), array('info', LOG_INFO), array('debug', LOG_DEBUG) ); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php0000644000000000000000000001000213365153155024443 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Log.Engine * @since CakePHP(tm) v 1.3 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('ConsoleLog', 'Log/Engine'); /** * TestConsoleLog * * @package Cake.Test.Case.Log.Engine */ class TestConsoleLog extends ConsoleLog { } /** * TestCakeLog * * @package Cake.Test.Case.Log.Engine */ class TestCakeLog extends CakeLog { public static function replace($key, &$engine) { static::$_Collection->{$key} = $engine; } } /** * ConsoleLogTest class * * @package Cake.Test.Case.Log.Engine */ class ConsoleLogTest extends CakeTestCase { public function setUp() { parent::setUp(); CakeLog::config('debug', array( 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( 'engine' => 'File', 'types' => array('error', 'warning'), 'file' => 'error', )); } public function tearDown() { parent::tearDown(); if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } if (file_exists(LOGS . 'debug.log')) { unlink(LOGS . 'debug.log'); } } /** * Test writing to ConsoleOutput * * @return void */ public function testConsoleOutputWrites() { TestCakeLog::config('test_console_log', array( 'engine' => 'TestConsole', )); $mock = $this->getMock('TestConsoleLog', array('write'), array( array('types' => 'error'), )); TestCakeLog::replace('test_console_log', $mock); $message = 'Test error message'; $mock->expects($this->once()) ->method('write'); TestCakeLog::write(LOG_ERR, $message); } /** * Test logging to both ConsoleLog and FileLog * * @return void */ public function testCombinedLogWriting() { TestCakeLog::config('test_console_log', array( 'engine' => 'TestConsole', )); $mock = $this->getMock('TestConsoleLog', array('write'), array( array('types' => 'error'), )); TestCakeLog::replace('test_console_log', $mock); // log to both file and console $message = 'Test error message'; $mock->expects($this->once()) ->method('write'); TestCakeLog::write(LOG_ERR, $message); $this->assertTrue(file_exists(LOGS . 'error.log'), 'error.log missing'); $logOutput = file_get_contents(LOGS . 'error.log'); $this->assertContains($message, $logOutput); // TestConsoleLog is only interested in `error` type $message = 'Test info message'; $mock->expects($this->never()) ->method('write'); TestCakeLog::write(LOG_INFO, $message); // checks that output is correctly written in the correct logfile $this->assertTrue(file_exists(LOGS . 'error.log'), 'error.log missing'); $this->assertTrue(file_exists(LOGS . 'debug.log'), 'debug.log missing'); $logOutput = file_get_contents(LOGS . 'error.log'); $this->assertNotContains($message, $logOutput); $logOutput = file_get_contents(LOGS . 'debug.log'); $this->assertContains($message, $logOutput); } /** * test default value of stream 'outputAs' * * @return void */ public function testDefaultOutputAs() { TestCakeLog::config('test_console_log', array( 'engine' => 'TestConsole', )); if ((DS === '\\' && !(bool)env('ANSICON') && env('ConEmuANSI') !== 'ON') || (function_exists('posix_isatty') && !posix_isatty(null)) ) { $expected = ConsoleOutput::PLAIN; } else { $expected = ConsoleOutput::COLOR; } $stream = TestCakeLog::stream('test_console_log'); $config = $stream->config(); $this->assertEquals($expected, $config['outputAs']); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Log/LogEngineCollectionTest.php0000644000000000000000000000420113365153155025061 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Log * @since CakePHP(tm) v 2.4 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('LogEngineCollection', 'Log'); App::uses('FileLog', 'Log/Engine'); /** * LoggerEngineLog class */ class LoggerEngineLog extends FileLog { } /** * LogEngineCollectionTest class * * @package Cake.Test.Case.Log */ class LogEngineCollectionTest extends CakeTestCase { public $Collection; /** * Start test callback * * @return void */ public function setUp() { parent::setUp(); $this->Collection = new LogEngineCollection(); } /** * test load * * @return void */ public function testLoad() { $result = $this->Collection->load('key', array('engine' => 'File')); $this->assertInstanceOf('CakeLogInterface', $result); } /** * test load with deprecated Log suffix * * @return void */ public function testLoadWithSuffix() { $result = $this->Collection->load('key', array('engine' => 'FileLog')); $this->assertInstanceOf('CakeLogInterface', $result); } /** * test that engines starting with Log also work properly * * @return void */ public function testLoadWithSuffixAtBeginning() { $result = $this->Collection->load('key', array('engine' => 'LoggerEngine')); $this->assertInstanceOf('CakeLogInterface', $result); } /** * test load with invalid Log * * @return void * @expectedException CakeLogException */ public function testLoadInvalid() { $result = $this->Collection->load('key', array('engine' => 'ImaginaryFile')); $this->assertInstanceOf('CakeLogInterface', $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Log/CakeLogTest.php0000644000000000000000000004777213365153155022527 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Log * @since CakePHP(tm) v 1.2.0.5432 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('CakeLog', 'Log'); App::uses('FileLog', 'Log/Engine'); /** * CakeLogTest class * * @package Cake.Test.Case.Log */ class CakeLogTest extends CakeTestCase { /** * Start test callback, clears all streams enabled. * * @return void */ public function setUp() { parent::setUp(); $streams = CakeLog::configured(); foreach ($streams as $stream) { CakeLog::drop($stream); } } /** * test importing loggers from app/libs and plugins. * * @return void */ public function testImportingLoggers() { App::build(array( 'Lib' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Lib' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load('TestPlugin'); $result = CakeLog::config('libtest', array( 'engine' => 'TestAppLog' )); $this->assertTrue($result); $this->assertEquals(CakeLog::configured(), array('libtest')); $result = CakeLog::config('plugintest', array( 'engine' => 'TestPlugin.TestPluginLog' )); $this->assertTrue($result); $this->assertEquals(CakeLog::configured(), array('libtest', 'plugintest')); CakeLog::write(LOG_INFO, 'TestPluginLog is not a BaseLog descendant'); App::build(); CakePlugin::unload(); } /** * test all the errors from failed logger imports * * @expectedException CakeLogException * @return void */ public function testImportingLoggerFailure() { CakeLog::config('fail', array()); } /** * test config() with valid key name * * @return void */ public function testValidKeyName() { CakeLog::config('valid', array('engine' => 'File')); $stream = CakeLog::stream('valid'); $this->assertInstanceOf('FileLog', $stream); CakeLog::drop('valid'); } /** * test config() with valid key name including the deprecated Log suffix * * @return void */ public function testValidKeyNameLogSuffix() { CakeLog::config('valid', array('engine' => 'FileLog')); $stream = CakeLog::stream('valid'); $this->assertInstanceOf('FileLog', $stream); CakeLog::drop('valid'); } /** * test config() with invalid key name * * @expectedException CakeLogException * @return void */ public function testInvalidKeyName() { CakeLog::config('1nv', array('engine' => 'File')); } /** * test that loggers have to implement the correct interface. * * @expectedException CakeLogException * @return void */ public function testNotImplementingInterface() { CakeLog::config('fail', array('engine' => 'stdClass')); } /** * Test that CakeLog does not auto create logs when no streams are there to listen. * * @return void */ public function testNoStreamListenting() { if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } $res = CakeLog::write(LOG_WARNING, 'Test warning'); $this->assertFalse($res); $this->assertFalse(file_exists(LOGS . 'error.log')); $result = CakeLog::configured(); $this->assertEquals(array(), $result); } /** * test configuring log streams * * @return void */ public function testConfig() { CakeLog::config('file', array( 'engine' => 'File', 'path' => LOGS )); $result = CakeLog::configured(); $this->assertEquals(array('file'), $result); if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } CakeLog::write(LOG_WARNING, 'Test warning'); $this->assertTrue(file_exists(LOGS . 'error.log')); $result = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning/', $result); unlink(LOGS . 'error.log'); } /** * explicit tests for drop() * * @return void */ public function testDrop() { CakeLog::config('file', array( 'engine' => 'File', 'path' => LOGS )); $result = CakeLog::configured(); $this->assertEquals(array('file'), $result); CakeLog::drop('file'); $result = CakeLog::configured(); $this->assertSame(array(), $result); } /** * testLogFileWriting method * * @return void */ public function testLogFileWriting() { CakeLog::config('file', array( 'engine' => 'File', 'path' => LOGS )); if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } $result = CakeLog::write(LOG_WARNING, 'Test warning'); $this->assertTrue($result); $this->assertTrue(file_exists(LOGS . 'error.log')); unlink(LOGS . 'error.log'); CakeLog::write(LOG_WARNING, 'Test warning 1'); CakeLog::write(LOG_WARNING, 'Test warning 2'); $result = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning 1/', $result); $this->assertRegExp('/2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning 2$/', $result); unlink(LOGS . 'error.log'); } /** * test selective logging by level/type * * @return void */ public function testSelectiveLoggingByLevel() { if (file_exists(LOGS . 'spam.log')) { unlink(LOGS . 'spam.log'); } if (file_exists(LOGS . 'eggs.log')) { unlink(LOGS . 'eggs.log'); } CakeLog::config('spam', array( 'engine' => 'File', 'types' => 'debug', 'file' => 'spam', )); CakeLog::config('eggs', array( 'engine' => 'File', 'types' => array('eggs', 'debug', 'error', 'warning'), 'file' => 'eggs', )); $testMessage = 'selective logging'; CakeLog::write(LOG_WARNING, $testMessage); $this->assertTrue(file_exists(LOGS . 'eggs.log')); $this->assertFalse(file_exists(LOGS . 'spam.log')); CakeLog::write(LOG_DEBUG, $testMessage); $this->assertTrue(file_exists(LOGS . 'spam.log')); $contents = file_get_contents(LOGS . 'spam.log'); $this->assertContains('Debug: ' . $testMessage, $contents); $contents = file_get_contents(LOGS . 'eggs.log'); $this->assertContains('Debug: ' . $testMessage, $contents); if (file_exists(LOGS . 'spam.log')) { unlink(LOGS . 'spam.log'); } if (file_exists(LOGS . 'eggs.log')) { unlink(LOGS . 'eggs.log'); } } /** * test enable * * @expectedException CakeLogException * @return void */ public function testStreamEnable() { CakeLog::config('spam', array( 'engine' => 'File', 'file' => 'spam', )); $this->assertTrue(CakeLog::enabled('spam')); CakeLog::drop('spam'); CakeLog::enable('bogus_stream'); } /** * test disable * * @expectedException CakeLogException * @return void */ public function testStreamDisable() { CakeLog::config('spam', array( 'engine' => 'File', 'file' => 'spam', )); $this->assertTrue(CakeLog::enabled('spam')); CakeLog::disable('spam'); $this->assertFalse(CakeLog::enabled('spam')); CakeLog::drop('spam'); CakeLog::enable('bogus_stream'); } /** * test enabled() invalid stream * * @expectedException CakeLogException * @return void */ public function testStreamEnabledInvalid() { CakeLog::enabled('bogus_stream'); } /** * test disable invalid stream * * @expectedException CakeLogException * @return void */ public function testStreamDisableInvalid() { CakeLog::disable('bogus_stream'); } /** * resets log config * * @return void */ protected function _resetLogConfig() { CakeLog::config('debug', array( 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( 'engine' => 'File', 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'file' => 'error', )); } /** * delete logs * * @return void */ protected function _deleteLogs() { if (file_exists(LOGS . 'shops.log')) { unlink(LOGS . 'shops.log'); } if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } if (file_exists(LOGS . 'debug.log')) { unlink(LOGS . 'debug.log'); } if (file_exists(LOGS . 'bogus.log')) { unlink(LOGS . 'bogus.log'); } if (file_exists(LOGS . 'spam.log')) { unlink(LOGS . 'spam.log'); } if (file_exists(LOGS . 'eggs.log')) { unlink(LOGS . 'eggs.log'); } } /** * test backward compatible scoped logging * * @return void */ public function testScopedLoggingBC() { $this->_resetLogConfig(); CakeLog::config('shops', array( 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops', )); $this->_deleteLogs(); CakeLog::write('info', 'info message'); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertTrue(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::write('transactions', 'transaction message'); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->assertFalse(file_exists(LOGS . 'transactions.log')); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::write('error', 'error message'); $this->assertTrue(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->assertFalse(file_exists(LOGS . 'shops.log')); $this->_deleteLogs(); CakeLog::write('orders', 'order message'); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->assertFalse(file_exists(LOGS . 'orders.log')); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->_deleteLogs(); CakeLog::write('warning', 'warning message'); $this->assertTrue(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::drop('shops'); } /** * Test that scopes are exclusive and don't bleed. * * @return void */ public function testScopedLoggingExclusive() { $this->_deleteLogs(); CakeLog::config('shops', array( 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops.log', )); CakeLog::config('eggs', array( 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('eggs'), 'file' => 'eggs.log', )); CakeLog::write('info', 'transactions message', 'transactions'); $this->assertFalse(file_exists(LOGS . 'eggs.log')); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->_deleteLogs(); CakeLog::write('info', 'eggs message', 'eggs'); $this->assertTrue(file_exists(LOGS . 'eggs.log')); $this->assertFalse(file_exists(LOGS . 'shops.log')); } /** * test scoped logging * * @return void */ public function testScopedLogging() { $this->_resetLogConfig(); $this->_deleteLogs(); CakeLog::config('string-scope', array( 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => 'string-scope', 'file' => 'string-scope.log' )); CakeLog::write('info', 'info message', 'string-scope'); $this->assertTrue(file_exists(LOGS . 'string-scope.log')); CakeLog::drop('string-scope'); CakeLog::config('shops', array( 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops.log', )); CakeLog::write('info', 'info message', 'transactions'); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->assertTrue(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::write('transactions', 'transaction message', 'orders'); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->assertFalse(file_exists(LOGS . 'transactions.log')); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::write('error', 'error message', 'orders'); $this->assertTrue(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->assertFalse(file_exists(LOGS . 'shops.log')); $this->_deleteLogs(); CakeLog::write('orders', 'order message', 'transactions'); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->assertFalse(file_exists(LOGS . 'orders.log')); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->_deleteLogs(); CakeLog::write('warning', 'warning message', 'orders'); $this->assertTrue(file_exists(LOGS . 'error.log')); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::drop('shops'); } /** * test bogus type and scope * * @return void */ public function testBogusTypeAndScope() { $this->_resetLogConfig(); $this->_deleteLogs(); CakeLog::config('file', array( 'engine' => 'File', 'path' => LOGS )); CakeLog::write('bogus', 'bogus message'); $this->assertTrue(file_exists(LOGS . 'bogus.log')); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::write('bogus', 'bogus message', 'bogus'); $this->assertTrue(file_exists(LOGS . 'bogus.log')); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::write('error', 'bogus message', 'bogus'); $this->assertFalse(file_exists(LOGS . 'bogus.log')); $this->assertTrue(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); } /** * test scoped logging with convenience methods * * @return void */ public function testConvenienceScopedLogging() { if (file_exists(LOGS . 'shops.log')) { unlink(LOGS . 'shops.log'); } if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } if (file_exists(LOGS . 'debug.log')) { unlink(LOGS . 'debug.log'); } $this->_resetLogConfig(); CakeLog::config('shops', array( 'engine' => 'File', 'types' => array('info', 'debug', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops', )); CakeLog::info('info message', 'transactions'); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->assertTrue(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::error('error message', 'orders'); $this->assertTrue(file_exists(LOGS . 'error.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->assertFalse(file_exists(LOGS . 'shops.log')); $this->_deleteLogs(); CakeLog::warning('warning message', 'orders'); $this->assertTrue(file_exists(LOGS . 'error.log')); $this->assertTrue(file_exists(LOGS . 'shops.log')); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); CakeLog::drop('shops'); } /** * test convenience methods * * @return void */ public function testConvenienceMethods() { $this->_deleteLogs(); CakeLog::config('debug', array( 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( 'engine' => 'File', 'types' => array('emergency', 'alert', 'critical', 'error', 'warning'), 'file' => 'error', )); $testMessage = 'emergency message'; CakeLog::emergency($testMessage); $contents = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/(Emergency|Critical): ' . $testMessage . '/', $contents); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); $testMessage = 'alert message'; CakeLog::alert($testMessage); $contents = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/(Alert|Critical): ' . $testMessage . '/', $contents); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); $testMessage = 'critical message'; CakeLog::critical($testMessage); $contents = file_get_contents(LOGS . 'error.log'); $this->assertContains('Critical: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); $testMessage = 'error message'; CakeLog::error($testMessage); $contents = file_get_contents(LOGS . 'error.log'); $this->assertContains('Error: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); $testMessage = 'warning message'; CakeLog::warning($testMessage); $contents = file_get_contents(LOGS . 'error.log'); $this->assertContains('Warning: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); $testMessage = 'notice message'; CakeLog::notice($testMessage); $contents = file_get_contents(LOGS . 'debug.log'); $this->assertRegExp('/(Notice|Debug): ' . $testMessage . '/', $contents); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->_deleteLogs(); $testMessage = 'info message'; CakeLog::info($testMessage); $contents = file_get_contents(LOGS . 'debug.log'); $this->assertRegExp('/(Info|Debug): ' . $testMessage . '/', $contents); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->_deleteLogs(); $testMessage = 'debug message'; CakeLog::debug($testMessage); $contents = file_get_contents(LOGS . 'debug.log'); $this->assertContains('Debug: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->_deleteLogs(); } /** * test levels customization * * @return void */ public function testLevelCustomization() { $this->skipIf(DIRECTORY_SEPARATOR === '\\', 'Log level tests not supported on Windows.'); $levels = CakeLog::defaultLevels(); $this->assertNotEmpty($levels); $result = array_keys($levels); $this->assertEquals(array(0, 1, 2, 3, 4, 5, 6, 7), $result); $levels = CakeLog::levels(array('foo', 'bar')); CakeLog::defaultLevels(); $this->assertEquals('foo', $levels[8]); $this->assertEquals('bar', $levels[9]); $levels = CakeLog::levels(array(11 => 'spam', 'bar' => 'eggs')); CakeLog::defaultLevels(); $this->assertEquals('spam', $levels[8]); $this->assertEquals('eggs', $levels[9]); $levels = CakeLog::levels(array(11 => 'spam', 'bar' => 'eggs'), false); CakeLog::defaultLevels(); $this->assertEquals(array('spam', 'eggs'), $levels); $levels = CakeLog::levels(array('ham', 9 => 'spam', '12' => 'fam'), false); CakeLog::defaultLevels(); $this->assertEquals(array('ham', 'spam', 'fam'), $levels); } /** * Test writing log files with custom levels * * @return void */ public function testCustomLevelWrites() { $this->_deleteLogs(); $this->_resetLogConfig(); CakeLog::levels(array('spam', 'eggs')); $testMessage = 'error message'; CakeLog::write('error', $testMessage); CakeLog::defaultLevels(); $this->assertTrue(file_exists(LOGS . 'error.log')); $contents = file_get_contents(LOGS . 'error.log'); $this->assertContains('Error: ' . $testMessage, $contents); CakeLog::config('spam', array( 'engine' => 'File', 'file' => 'spam.log', 'types' => 'spam', )); CakeLog::config('eggs', array( 'engine' => 'File', 'file' => 'eggs.log', 'types' => array('spam', 'eggs'), )); $testMessage = 'spam message'; CakeLog::write('spam', $testMessage); CakeLog::defaultLevels(); $this->assertTrue(file_exists(LOGS . 'spam.log')); $this->assertTrue(file_exists(LOGS . 'eggs.log')); $contents = file_get_contents(LOGS . 'spam.log'); $this->assertContains('Spam: ' . $testMessage, $contents); $testMessage = 'egg message'; CakeLog::write('eggs', $testMessage); CakeLog::defaultLevels(); $contents = file_get_contents(LOGS . 'spam.log'); $this->assertNotContains('Eggs: ' . $testMessage, $contents); $contents = file_get_contents(LOGS . 'eggs.log'); $this->assertContains('Eggs: ' . $testMessage, $contents); CakeLog::drop('spam'); CakeLog::drop('eggs'); $this->_deleteLogs(); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Core/0000755000000000000000000000000013365153155017777 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Core/AppTest.php0000644000000000000000000006104313365153155022074 0ustar rootrootassertEquals($expected, $old); App::build(array('Model' => array('/path/to/models/'))); $new = App::path('Model'); $expected = array( '/path/to/models/', APP . 'Model' . DS ); $this->assertEquals($expected, $new); App::build(); App::build(array('Model' => array('/path/to/models/')), App::PREPEND); $new = App::path('Model'); $expected = array( '/path/to/models/', APP . 'Model' . DS ); $this->assertEquals($expected, $new); App::build(); App::build(array('Model' => array('/path/to/models/')), App::APPEND); $new = App::path('Model'); $expected = array( APP . 'Model' . DS, '/path/to/models/' ); $this->assertEquals($expected, $new); App::build(); App::build(array( 'Model' => array('/path/to/models/'), 'Controller' => array('/path/to/controllers/'), ), App::APPEND); $new = App::path('Model'); $expected = array( APP . 'Model' . DS, '/path/to/models/' ); $this->assertEquals($expected, $new); $new = App::path('Controller'); $expected = array( APP . 'Controller' . DS, '/path/to/controllers/' ); $this->assertEquals($expected, $new); App::build(); //reset defaults $defaults = App::path('Model'); $this->assertEquals($old, $defaults); } /** * tests that it is possible to set up paths using the CakePHP 1.3 notation for them (models, behaviors, controllers...) * * @return void */ public function testCompatibleBuild() { $old = App::path('models'); $expected = array( APP . 'Model' . DS ); $this->assertEquals($expected, $old); App::build(array('models' => array('/path/to/models/'))); $new = App::path('models'); $expected = array( '/path/to/models/', APP . 'Model' . DS ); $this->assertEquals($expected, $new); $this->assertEquals($expected, App::path('Model')); App::build(array('datasources' => array('/path/to/datasources/'))); $expected = array( '/path/to/datasources/', APP . 'Model' . DS . 'Datasource' . DS ); $result = App::path('datasources'); $this->assertEquals($expected, $result); $this->assertEquals($expected, App::path('Model/Datasource')); App::build(array('behaviors' => array('/path/to/behaviors/'))); $expected = array( '/path/to/behaviors/', APP . 'Model' . DS . 'Behavior' . DS ); $result = App::path('behaviors'); $this->assertEquals($expected, $result); $this->assertEquals($expected, App::path('Model/Behavior')); App::build(array('controllers' => array('/path/to/controllers/'))); $expected = array( '/path/to/controllers/', APP . 'Controller' . DS ); $result = App::path('controllers'); $this->assertEquals($expected, $result); $this->assertEquals($expected, App::path('Controller')); App::build(array('components' => array('/path/to/components/'))); $expected = array( '/path/to/components/', APP . 'Controller' . DS . 'Component' . DS ); $result = App::path('components'); $this->assertEquals($expected, $result); $this->assertEquals($expected, App::path('Controller/Component')); App::build(array('views' => array('/path/to/views/'))); $expected = array( '/path/to/views/', APP . 'View' . DS ); $result = App::path('views'); $this->assertEquals($expected, $result); $this->assertEquals($expected, App::path('View')); App::build(array('helpers' => array('/path/to/helpers/'))); $expected = array( '/path/to/helpers/', APP . 'View' . DS . 'Helper' . DS ); $result = App::path('helpers'); $this->assertEquals($expected, $result); $this->assertEquals($expected, App::path('View/Helper')); App::build(array('shells' => array('/path/to/shells/'))); $expected = array( '/path/to/shells/', APP . 'Console' . DS . 'Command' . DS ); $result = App::path('shells'); $this->assertEquals($expected, $result); $this->assertEquals($expected, App::path('Console/Command')); App::build(); //reset defaults $defaults = App::path('Model'); $this->assertEquals($old, $defaults); } /** * test package build() with App::REGISTER. * * @return void */ public function testBuildPackage() { $pluginPaths = array( '/foo/bar', APP . 'Plugin' . DS, dirname(dirname(CAKE)) . DS . 'plugins' . DS ); App::build(array( 'Plugin' => array( '/foo/bar' ) )); $result = App::path('Plugin'); $this->assertEquals($pluginPaths, $result); $paths = App::path('Service'); $this->assertEquals(array(), $paths); App::build(array( 'Service' => array( '%s' . 'Service' . DS, ), ), App::REGISTER); $expected = array( APP . 'Service' . DS, ); $result = App::path('Service'); $this->assertEquals($expected, $result); //Ensure new paths registered for other packages are not affected $result = App::path('Plugin'); $this->assertEquals($pluginPaths, $result); App::build(); $paths = App::path('Service'); $this->assertEquals(array(), $paths); } /** * test path() with a plugin. * * @return void */ public function testPathWithPlugins() { $basepath = CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS; App::build(array( 'Plugin' => array($basepath), )); CakePlugin::load('TestPlugin'); $result = App::path('Vendor', 'TestPlugin'); $this->assertEquals($basepath . 'TestPlugin' . DS . 'Vendor' . DS, $result[0]); } /** * testBuildWithReset method * * @return void */ public function testBuildWithReset() { $old = App::path('Model'); $expected = array( APP . 'Model' . DS ); $this->assertEquals($expected, $old); App::build(array('Model' => array('/path/to/models/')), App::RESET); $new = App::path('Model'); $expected = array( '/path/to/models/' ); $this->assertEquals($expected, $new); App::build(); //reset defaults $defaults = App::path('Model'); $this->assertEquals($old, $defaults); } /** * testCore method * * @return void */ public function testCore() { $model = App::core('Model'); $this->assertEquals(array(CAKE . 'Model' . DS), $model); $view = App::core('View'); $this->assertEquals(array(CAKE . 'View' . DS), $view); $controller = App::core('Controller'); $this->assertEquals(array(CAKE . 'Controller' . DS), $controller); $component = App::core('Controller/Component'); $this->assertEquals(array(CAKE . 'Controller' . DS . 'Component' . DS), str_replace('/', DS, $component)); $auth = App::core('Controller/Component/Auth'); $this->assertEquals(array(CAKE . 'Controller' . DS . 'Component' . DS . 'Auth' . DS), str_replace('/', DS, $auth)); $datasource = App::core('Model/Datasource'); $this->assertEquals(array(CAKE . 'Model' . DS . 'Datasource' . DS), str_replace('/', DS, $datasource)); } /** * testListObjects method * * @return void */ public function testListObjects() { $result = App::objects('class', CAKE . 'Routing', false); $this->assertTrue(in_array('Dispatcher', $result)); $this->assertTrue(in_array('Router', $result)); App::build(array( 'Model/Behavior' => App::core('Model/Behavior'), 'Controller' => App::core('Controller'), 'Controller/Component' => App::core('Controller/Component'), 'View' => App::core('View'), 'Model' => App::core('Model'), 'View/Helper' => App::core('View/Helper'), ), App::RESET); $result = App::objects('behavior', null, false); $this->assertTrue(in_array('TreeBehavior', $result)); $result = App::objects('Model/Behavior', null, false); $this->assertTrue(in_array('TreeBehavior', $result)); $result = App::objects('component', null, false); $this->assertTrue(in_array('AuthComponent', $result)); $result = App::objects('Controller/Component', null, false); $this->assertTrue(in_array('AuthComponent', $result)); $result = App::objects('view', null, false); $this->assertTrue(in_array('MediaView', $result)); $result = App::objects('View', null, false); $this->assertTrue(in_array('MediaView', $result)); $result = App::objects('helper', null, false); $this->assertTrue(in_array('HtmlHelper', $result)); $result = App::objects('View/Helper', null, false); $this->assertTrue(in_array('HtmlHelper', $result)); $result = App::objects('model', null, false); $this->assertTrue(in_array('AcoAction', $result)); $result = App::objects('Model', null, false); $this->assertTrue(in_array('AcoAction', $result)); $result = App::objects('file'); $this->assertFalse($result); $result = App::objects('file', 'non_existing_configure'); $expected = array(); $this->assertEquals($expected, $result); $result = App::objects('NonExistingType'); $this->assertSame(array(), $result); App::build(array( 'plugins' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Lib' . DS ) )); $result = App::objects('plugin', null, false); $this->assertTrue(in_array('Cache', $result)); $this->assertTrue(in_array('Log', $result)); App::build(); } /** * Make sure that .svn and friends are excluded from App::objects('plugin') * * @return void */ public function testListObjectsIgnoreDotDirectories() { $path = CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS; $this->skipIf(!is_writable($path), $path . ' is not writable.'); App::build(array( 'plugins' => array($path) ), App::RESET); mkdir($path . '.svn'); $result = App::objects('plugin', null, false); rmdir($path . '.svn'); $this->assertNotContains('.svn', $result); } /** * Tests listing objects within a plugin * * @return void */ public function testListObjectsInPlugin() { App::build(array( 'Model' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS), 'plugins' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $result = App::objects('TestPlugin.model'); $this->assertTrue(in_array('TestPluginPost', $result)); $result = App::objects('TestPlugin.Model'); $this->assertTrue(in_array('TestPluginPost', $result)); $result = App::objects('TestPlugin.behavior'); $this->assertTrue(in_array('TestPluginPersisterOneBehavior', $result)); $result = App::objects('TestPlugin.Model/Behavior'); $this->assertTrue(in_array('TestPluginPersisterOneBehavior', $result)); $result = App::objects('TestPlugin.helper'); $expected = array('OtherHelperHelper', 'PluggedHelperHelper', 'TestPluginAppHelper'); $this->assertEquals($expected, $result); $result = App::objects('TestPlugin.View/Helper'); $expected = array('OtherHelperHelper', 'PluggedHelperHelper', 'TestPluginAppHelper'); $this->assertEquals($expected, $result); $result = App::objects('TestPlugin.component'); $this->assertTrue(in_array('OtherComponent', $result)); $result = App::objects('TestPlugin.Controller/Component'); $this->assertTrue(in_array('OtherComponent', $result)); $result = App::objects('TestPluginTwo.behavior'); $this->assertSame(array(), $result); $result = App::objects('TestPluginTwo.Model/Behavior'); $this->assertSame(array(), $result); $result = App::objects('model', null, false); $this->assertTrue(in_array('Comment', $result)); $this->assertTrue(in_array('Post', $result)); $result = App::objects('Model', null, false); $this->assertTrue(in_array('Comment', $result)); $this->assertTrue(in_array('Post', $result)); App::build(); } /** * test that themePath can find paths for themes. * * @return void */ public function testThemePath() { App::build(array( 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) )); $path = App::themePath('test_theme'); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS; $this->assertEquals($expected, $path); $path = App::themePath('TestTheme'); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS; $this->assertEquals($expected, $path); App::build(); } /** * testClassLoading method * * @return void */ public function testClassLoading() { $file = App::import('Model', 'Model', false); $this->assertTrue($file); $this->assertTrue(class_exists('Model')); $file = App::import('Controller', 'Controller', false); $this->assertTrue($file); $this->assertTrue(class_exists('Controller')); $file = App::import('Component', 'Auth', false); $this->assertTrue($file); $this->assertTrue(class_exists('AuthComponent')); $file = App::import('Shell', 'Shell', false); $this->assertTrue($file); $this->assertTrue(class_exists('Shell')); $file = App::import('Configure', 'PhpReader'); $this->assertTrue($file); $this->assertTrue(class_exists('PhpReader')); $file = App::import('Model', 'SomeRandomModelThatDoesNotExist', false); $this->assertFalse($file); $file = App::import('Model', 'AppModel', false); $this->assertTrue($file); $this->assertTrue(class_exists('AppModel')); $file = App::import('WrongType', null, true, array(), ''); $this->assertFalse($file); $file = App::import('Model', 'NonExistingPlugin.NonExistingModel', false); $this->assertFalse($file); $file = App::import('Model', array('NonExistingPlugin.NonExistingModel'), false); $this->assertFalse($file); if (!class_exists('AppController', false)) { $classes = array_flip(get_declared_classes()); $this->assertFalse(isset($classes['PagesController'])); $this->assertFalse(isset($classes['AppController'])); $file = App::import('Controller', 'Pages'); $this->assertTrue($file); $this->assertTrue(class_exists('PagesController')); $classes = array_flip(get_declared_classes()); $this->assertTrue(isset($classes['PagesController'])); $this->assertTrue(isset($classes['AppController'])); $file = App::import('Behavior', 'Containable'); $this->assertTrue($file); $this->assertTrue(class_exists('ContainableBehavior')); $file = App::import('Component', 'RequestHandler'); $this->assertTrue($file); $this->assertTrue(class_exists('RequestHandlerComponent')); $file = App::import('Helper', 'Form'); $this->assertTrue($file); $this->assertTrue(class_exists('FormHelper')); $file = App::import('Model', 'NonExistingModel'); $this->assertFalse($file); $file = App::import('Datasource', 'DboSource'); $this->assertTrue($file); $this->assertTrue(class_exists('DboSource')); } App::build(); } /** * test import() with plugins * * @return void */ public function testPluginImporting() { App::build(array( 'Lib' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Lib' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) )); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $result = App::import('Controller', 'TestPlugin.Tests'); $this->assertTrue($result); $this->assertTrue(class_exists('TestPluginAppController')); $this->assertTrue(class_exists('TestsController')); $result = App::import('Lib', 'TestPlugin.TestPluginLibrary'); $this->assertTrue($result); $this->assertTrue(class_exists('TestPluginLibrary')); $result = App::import('Lib', 'Library'); $this->assertTrue($result); $this->assertTrue(class_exists('Library')); $result = App::import('Helper', 'TestPlugin.OtherHelper'); $this->assertTrue($result); $this->assertTrue(class_exists('OtherHelperHelper')); $result = App::import('Helper', 'TestPlugin.TestPluginApp'); $this->assertTrue($result); $this->assertTrue(class_exists('TestPluginAppHelper')); $result = App::import('Datasource', 'TestPlugin.TestSource'); $this->assertTrue($result); $this->assertTrue(class_exists('TestSource')); App::uses('ExampleExample', 'TestPlugin.Vendor/Example'); $this->assertTrue(class_exists('ExampleExample')); App::build(); } /** * test that building helper paths actually works. * * @return void */ public function testImportingHelpersFromAlternatePaths() { $this->assertFalse(class_exists('BananaHelper', false), 'BananaHelper exists, cannot test importing it.'); App::build(array( 'View/Helper' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Helper' . DS ) )); $this->assertFalse(class_exists('BananaHelper', false), 'BananaHelper exists, cannot test importing it.'); App::import('Helper', 'Banana'); $this->assertTrue(class_exists('BananaHelper', false), 'BananaHelper was not loaded.'); App::build(); } /** * testFileLoading method * * @return void */ public function testFileLoading() { $file = App::import('File', 'RealFile', false, array(), CAKE . 'Config' . DS . 'config.php'); $this->assertTrue($file); $file = App::import('File', 'NoFile', false, array(), CAKE . 'Config' . DS . 'cake' . DS . 'config.php'); $this->assertFalse($file); } /** * testFileLoadingWithArray method * * @return void */ public function testFileLoadingWithArray() { $type = array( 'type' => 'File', 'name' => 'SomeName', 'parent' => false, 'file' => CAKE . DS . 'Config' . DS . 'config.php' ); $file = App::import($type); $this->assertTrue($file); $type = array( 'type' => 'File', 'name' => 'NoFile', 'parent' => false, 'file' => CAKE . 'Config' . DS . 'cake' . DS . 'config.php' ); $file = App::import($type); $this->assertFalse($file); } /** * testFileLoadingReturnValue method * * @return void */ public function testFileLoadingReturnValue() { $file = App::import('File', 'Name', false, array(), CAKE . 'Config' . DS . 'config.php', true); $this->assertTrue(!empty($file)); $this->assertTrue(isset($file['Cake.version'])); $type = array( 'type' => 'File', 'name' => 'OtherName', 'parent' => false, 'file' => CAKE . 'Config' . DS . 'config.php', 'return' => true ); $file = App::import($type); $this->assertTrue(!empty($file)); $this->assertTrue(isset($file['Cake.version'])); } /** * testLoadingWithSearch method * * @return void */ public function testLoadingWithSearch() { $file = App::import('File', 'NewName', false, array(CAKE . 'Config' . DS), 'config.php'); $this->assertTrue($file); $file = App::import('File', 'AnotherNewName', false, array(CAKE), 'config.php'); $this->assertFalse($file); } /** * testLoadingWithSearchArray method * * @return void */ public function testLoadingWithSearchArray() { $type = array( 'type' => 'File', 'name' => 'RandomName', 'parent' => false, 'file' => 'config.php', 'search' => array(CAKE . 'Config' . DS) ); $file = App::import($type); $this->assertTrue($file); $type = array( 'type' => 'File', 'name' => 'AnotherRandomName', 'parent' => false, 'file' => 'config.php', 'search' => array(CAKE) ); $file = App::import($type); $this->assertFalse($file); } /** * testMultipleLoading method * * @return void */ public function testMultipleLoading() { if (class_exists('PersisterOne', false) || class_exists('PersisterTwo', false)) { $this->markTestSkipped('Cannot test loading of classes that exist.'); } App::build(array( 'Model' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS) )); $toLoad = array('PersisterOne', 'PersisterTwo'); $load = App::import('Model', $toLoad); $this->assertTrue($load); $classes = array_flip(get_declared_classes()); $this->assertTrue(isset($classes['PersisterOne'])); $this->assertTrue(isset($classes['PersisterTwo'])); $load = App::import('Model', array('PersisterOne', 'SomeNotFoundClass', 'PersisterTwo')); $this->assertFalse($load); } public function testLoadingVendor() { App::build(array( 'plugins' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'vendors' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS), ), App::RESET); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); ob_start(); $result = App::import('Vendor', 'css/TestAsset', array('ext' => 'css')); $text = ob_get_clean(); $this->assertTrue($result); $this->assertEquals('/* this is the test asset css file */', trim($text)); $result = App::import('Vendor', 'TestPlugin.sample/SamplePlugin'); $this->assertTrue($result); $this->assertTrue(class_exists('SamplePluginClassTestName')); $result = App::import('Vendor', 'sample/ConfigureTestVendorSample'); $this->assertTrue($result); $this->assertTrue(class_exists('ConfigureTestVendorSample')); ob_start(); $result = App::import('Vendor', 'SomeNameInSubfolder', array('file' => 'somename/some.name.php')); $text = ob_get_clean(); $this->assertTrue($result); $this->assertEquals('This is a file with dot in file name', $text); ob_start(); $result = App::import('Vendor', 'TestHello', array('file' => 'Test' . DS . 'hello.php')); $text = ob_get_clean(); $this->assertTrue($result); $this->assertEquals('This is the hello.php file in Test directory', $text); ob_start(); $result = App::import('Vendor', 'MyTest', array('file' => 'Test' . DS . 'MyTest.php')); $text = ob_get_clean(); $this->assertTrue($result); $this->assertEquals('This is the MyTest.php file', $text); ob_start(); $result = App::import('Vendor', 'Welcome'); $text = ob_get_clean(); $this->assertTrue($result); $this->assertEquals('This is the welcome.php file in vendors directory', $text); ob_start(); $result = App::import('Vendor', 'TestPlugin.Welcome'); $text = ob_get_clean(); $this->assertTrue($result); $this->assertEquals('This is the welcome.php file in test_plugin/vendors directory', $text); } /** * Tests that the automatic class loader will also find in "libs" folder for both * app and plugins if it does not find the class in other configured paths * * @return void */ public function testLoadClassInLibs() { App::build(array( 'libs' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Lib' . DS), 'plugins' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $this->assertFalse(class_exists('CustomLibClass', false)); App::uses('CustomLibClass', 'TestPlugin.Custom/Package'); $this->assertTrue(class_exists('CustomLibClass')); $this->assertFalse(class_exists('TestUtilityClass', false)); App::uses('TestUtilityClass', 'Utility'); $this->assertTrue(class_exists('TestUtilityClass')); } /** * Tests that App::location() returns the defined path for a class * * @return void */ public function testClassLocation() { App::uses('MyCustomClass', 'MyPackage/Name'); $this->assertEquals('MyPackage/Name', App::location('MyCustomClass')); } /** * Test that paths() works. * * @return void */ public function testPaths() { $result = App::paths(); $this->assertArrayHasKey('Plugin', $result); $this->assertArrayHasKey('Controller', $result); $this->assertArrayHasKey('Controller/Component', $result); } /** * Proves that it is possible to load plugin libraries in top * level Lib dir for plugins * * @return void */ public function testPluginLibClasses() { App::build(array( 'plugins' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $this->assertFalse(class_exists('TestPluginOtherLibrary', false)); App::uses('TestPluginOtherLibrary', 'TestPlugin.Lib'); $this->assertTrue(class_exists('TestPluginOtherLibrary')); } /** * Test that increaseMemoryLimit increases the maximum amount of memory actually * * @dataProvider memoryVariationProvider * @return void */ public function testIncreaseMemoryLimit($memoryLimit, $additionalKb, $expected) { $this->skipIf(!function_exists('ini_set')); $originalMemoryLimit = ini_get('memory_limit'); ini_set('memory_limit', $memoryLimit); App::increaseMemoryLimit($additionalKb); $this->assertEquals($expected, ini_get('memory_limit')); ini_set('memory_limit', $originalMemoryLimit); } /** * Data provider function for testIncreaseMemoryLimit * * @return void */ public function memoryVariationProvider() { return array( array('131072K', 100000, '231072K'), array('256M', 1, '262145K'), array('1G', 1, '1048577K'), array('-1', 100000, '-1') ); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Core/CakeObjectTest.php0000644000000000000000000004527713365153155023361 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Core * @since CakePHP(tm) v 1.2.0.5432 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('CakeObject', 'Core'); App::uses('Object', 'Core'); App::uses('Router', 'Routing'); App::uses('Controller', 'Controller'); App::uses('Model', 'Model'); /** * RequestActionPost class * * @package Cake.Test.Case.Core */ class RequestActionPost extends CakeTestModel { /** * useTable property * * @var string */ public $useTable = 'posts'; } /** * RequestActionController class * * @package Cake.Test.Case.Core */ class RequestActionController extends Controller { /** * uses property * * @var array */ public $uses = array('RequestActionPost'); /** * test_request_action method * * @return void */ public function test_request_action() { return 'This is a test'; } /** * another_ra_test method * * @param mixed $id * @param mixed $other * @return void */ public function another_ra_test($id, $other) { return $id + $other; } /** * normal_request_action method * * @return string Hello World! */ public function normal_request_action() { return 'Hello World'; } /** * returns $this->here * * @return string $this->here. */ public function return_here() { return $this->request->here(); } /** * paginate_request_action method * * @return true */ public function paginate_request_action() { $this->paginate(); return true; } /** * post pass, testing post passing * * @return array */ public function post_pass() { return $this->request->data; } /** * test param passing and parsing. * * @return array */ public function params_pass() { return $this->request; } public function param_check() { $this->autoRender = false; $content = ''; if (isset($this->request->params[0])) { $content = 'return found'; } $this->response->body($content); } } /** * TestCakeObject class * * @package Cake.Test.Case.Core */ class TestCakeObject extends CakeObject { /** * firstName property * * @var string */ public $firstName = 'Joel'; /** * lastName property * * @var string */ public $lastName = 'Moss'; /** * methodCalls property * * @var array */ public $methodCalls = array(); /** * emptyMethod method * * @return void */ public function emptyMethod() { $this->methodCalls[] = 'emptyMethod'; } /** * oneParamMethod method * * @param mixed $param * @return void */ public function oneParamMethod($param) { $this->methodCalls[] = array('oneParamMethod' => array($param)); } /** * twoParamMethod method * * @param mixed $param * @param mixed $paramTwo * @return void */ public function twoParamMethod($param, $paramTwo) { $this->methodCalls[] = array('twoParamMethod' => array($param, $paramTwo)); } /** * threeParamMethod method * * @param mixed $param * @param mixed $paramTwo * @param mixed $paramThree * @return void */ public function threeParamMethod($param, $paramTwo, $paramThree) { $this->methodCalls[] = array('threeParamMethod' => array($param, $paramTwo, $paramThree)); } /** * fourParamMethod method * * @param mixed $param * @param mixed $paramTwo * @param mixed $paramThree * @param mixed $paramFour * @return void */ public function fourParamMethod($param, $paramTwo, $paramThree, $paramFour) { $this->methodCalls[] = array('fourParamMethod' => array($param, $paramTwo, $paramThree, $paramFour)); } /** * fiveParamMethod method * * @param mixed $param * @param mixed $paramTwo * @param mixed $paramThree * @param mixed $paramFour * @param mixed $paramFive * @return void */ public function fiveParamMethod($param, $paramTwo, $paramThree, $paramFour, $paramFive) { $this->methodCalls[] = array('fiveParamMethod' => array($param, $paramTwo, $paramThree, $paramFour, $paramFive)); } /** * crazyMethod method * * @param mixed $param * @param mixed $paramTwo * @param mixed $paramThree * @param mixed $paramFour * @param mixed $paramFive * @param mixed $paramSix * @param mixed $paramSeven * @return void */ public function crazyMethod($param, $paramTwo, $paramThree, $paramFour, $paramFive, $paramSix, $paramSeven = null) { $this->methodCalls[] = array('crazyMethod' => array($param, $paramTwo, $paramThree, $paramFour, $paramFive, $paramSix, $paramSeven)); } /** * methodWithOptionalParam method * * @param mixed $param * @return void */ public function methodWithOptionalParam($param = null) { $this->methodCalls[] = array('methodWithOptionalParam' => array($param)); } /** * Set properties. * * @param array $properties The $properties. * @return void */ public function set($properties = array()) { return parent::_set($properties); } } /** * ObjectTestModel class * * @package Cake.Test.Case.Core */ class ObjectTestModel extends CakeTestModel { public $useTable = false; } /** * CakeObject Test class * * @package Cake.Test.Case.Core */ class ObjectTest extends CakeTestCase { /** * fixtures * * @var string */ public $fixtures = array('core.post', 'core.test_plugin_comment', 'core.comment'); /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->object = new TestCakeObject(); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); CakePlugin::unload(); unset($this->object); } /** * testLog method * * @return void */ public function testLog() { if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } $this->assertTrue($this->object->log('Test warning 1')); $this->assertTrue($this->object->log(array('Test' => 'warning 2'))); $result = file(LOGS . 'error.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Error: Test warning 1$/', $result[0]); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Error: Array$/', $result[1]); $this->assertRegExp('/^\($/', $result[2]); $this->assertRegExp('/\[Test\] => warning 2$/', $result[3]); $this->assertRegExp('/^\)$/', $result[4]); unlink(LOGS . 'error.log'); $this->assertTrue($this->object->log('Test warning 1', LOG_WARNING)); $this->assertTrue($this->object->log(array('Test' => 'warning 2'), LOG_WARNING)); $result = file(LOGS . 'error.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning 1$/', $result[0]); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Array$/', $result[1]); $this->assertRegExp('/^\($/', $result[2]); $this->assertRegExp('/\[Test\] => warning 2$/', $result[3]); $this->assertRegExp('/^\)$/', $result[4]); unlink(LOGS . 'error.log'); } /** * testSet method * * @return void */ public function testSet() { $this->object->set('a string'); $this->assertEquals('Joel', $this->object->firstName); $this->object->set(array('firstName')); $this->assertEquals('Joel', $this->object->firstName); $this->object->set(array('firstName' => 'Ashley')); $this->assertEquals('Ashley', $this->object->firstName); $this->object->set(array('firstName' => 'Joel', 'lastName' => 'Moose')); $this->assertEquals('Joel', $this->object->firstName); $this->assertEquals('Moose', $this->object->lastName); } /** * testToString method * * @return void */ public function testToString() { $result = strtolower($this->object->toString()); $this->assertEquals('testcakeobject', $result); } /** * testMethodDispatching method * * @return void */ public function testMethodDispatching() { $this->object->emptyMethod(); $expected = array('emptyMethod'); $this->assertSame($expected, $this->object->methodCalls); $this->object->oneParamMethod('Hello'); $expected[] = array('oneParamMethod' => array('Hello')); $this->assertSame($expected, $this->object->methodCalls); $this->object->twoParamMethod(true, false); $expected[] = array('twoParamMethod' => array(true, false)); $this->assertSame($expected, $this->object->methodCalls); $this->object->threeParamMethod(true, false, null); $expected[] = array('threeParamMethod' => array(true, false, null)); $this->assertSame($expected, $this->object->methodCalls); $this->object->crazyMethod(1, 2, 3, 4, 5, 6, 7); $expected[] = array('crazyMethod' => array(1, 2, 3, 4, 5, 6, 7)); $this->assertSame($expected, $this->object->methodCalls); $this->object = new TestCakeObject(); $this->assertSame($this->object->methodCalls, array()); $this->object->dispatchMethod('emptyMethod'); $expected = array('emptyMethod'); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('oneParamMethod', array('Hello')); $expected[] = array('oneParamMethod' => array('Hello')); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('twoParamMethod', array(true, false)); $expected[] = array('twoParamMethod' => array(true, false)); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('threeParamMethod', array(true, false, null)); $expected[] = array('threeParamMethod' => array(true, false, null)); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('fourParamMethod', array(1, 2, 3, 4)); $expected[] = array('fourParamMethod' => array(1, 2, 3, 4)); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('fiveParamMethod', array(1, 2, 3, 4, 5)); $expected[] = array('fiveParamMethod' => array(1, 2, 3, 4, 5)); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('crazyMethod', array(1, 2, 3, 4, 5, 6, 7)); $expected[] = array('crazyMethod' => array(1, 2, 3, 4, 5, 6, 7)); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('methodWithOptionalParam', array('Hello')); $expected[] = array('methodWithOptionalParam' => array("Hello")); $this->assertSame($expected, $this->object->methodCalls); $this->object->dispatchMethod('methodWithOptionalParam'); $expected[] = array('methodWithOptionalParam' => array(null)); $this->assertSame($expected, $this->object->methodCalls); } /** * testRequestAction method * * @return void */ public function testRequestAction() { App::build(array( 'Model' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS), 'Controller' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Controller' . DS) ), App::RESET); $this->assertNull(Router::getRequest(), 'request stack should be empty.'); $result = $this->object->requestAction(''); $this->assertFalse($result); $result = $this->object->requestAction('/request_action/test_request_action'); $expected = 'This is a test'; $this->assertEquals($expected, $result); $result = $this->object->requestAction( Configure::read('App.fullBaseUrl') . '/request_action/test_request_action' ); $expected = 'This is a test'; $this->assertEquals($expected, $result); $result = $this->object->requestAction('/request_action/another_ra_test/2/5'); $expected = 7; $this->assertEquals($expected, $result); $result = $this->object->requestAction('/tests_apps/index', array('return')); $expected = 'This is the TestsAppsController index view '; $this->assertEquals($expected, $result); $result = $this->object->requestAction('/tests_apps/some_method'); $expected = 5; $this->assertEquals($expected, $result); $result = $this->object->requestAction('/request_action/paginate_request_action'); $this->assertTrue($result); $result = $this->object->requestAction('/request_action/normal_request_action'); $expected = 'Hello World'; $this->assertEquals($expected, $result); $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation'); } /** * Test that here() is calculated correctly in requestAction * * @return void */ public function testRequestActionHere() { $url = '/request_action/return_here?key=value'; $result = $this->object->requestAction($url); $this->assertStringEndsWith($url, $result); } /** * test requestAction() and plugins. * * @return void */ public function testRequestActionPlugins() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), ), App::RESET); CakePlugin::load('TestPlugin'); Router::reload(); $result = $this->object->requestAction('/test_plugin/tests/index', array('return')); $expected = 'test plugin index'; $this->assertEquals($expected, $result); $result = $this->object->requestAction('/test_plugin/tests/index/some_param', array('return')); $expected = 'test plugin index'; $this->assertEquals($expected, $result); $result = $this->object->requestAction( array('controller' => 'tests', 'action' => 'index', 'plugin' => 'test_plugin'), array('return') ); $expected = 'test plugin index'; $this->assertEquals($expected, $result); $result = $this->object->requestAction('/test_plugin/tests/some_method'); $expected = 25; $this->assertEquals($expected, $result); $result = $this->object->requestAction( array('controller' => 'tests', 'action' => 'some_method', 'plugin' => 'test_plugin') ); $expected = 25; $this->assertEquals($expected, $result); } /** * test requestAction() with arrays. * * @return void */ public function testRequestActionArray() { App::build(array( 'Model' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS), 'Controller' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Controller' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin')); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'test_request_action') ); $expected = 'This is a test'; $this->assertEquals($expected, $result); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'another_ra_test'), array('pass' => array('5', '7')) ); $expected = 12; $this->assertEquals($expected, $result); $result = $this->object->requestAction( array('controller' => 'tests_apps', 'action' => 'index'), array('return') ); $expected = 'This is the TestsAppsController index view '; $this->assertEquals($expected, $result); $result = $this->object->requestAction(array('controller' => 'tests_apps', 'action' => 'some_method')); $expected = 5; $this->assertEquals($expected, $result); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'normal_request_action') ); $expected = 'Hello World'; $this->assertEquals($expected, $result); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'paginate_request_action') ); $this->assertTrue($result); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'paginate_request_action'), array('pass' => array(5), 'named' => array('param' => 'value')) ); $this->assertTrue($result); } /** * Test that requestAction() does not forward the 0 => return value. * * @return void */ public function testRequestActionRemoveReturnParam() { $result = $this->object->requestAction( '/request_action/param_check', array('return') ); $this->assertEquals('', $result, 'Return key was found'); } /** * Test that requestAction() is populating $this->params properly * * @return void */ public function testRequestActionParamParseAndPass() { $result = $this->object->requestAction('/request_action/params_pass'); $this->assertEquals('request_action/params_pass', $result->url); $this->assertEquals('request_action', $result['controller']); $this->assertEquals('params_pass', $result['action']); $this->assertEquals(null, $result['plugin']); $result = $this->object->requestAction('/request_action/params_pass/sort:desc/limit:5'); $expected = array('sort' => 'desc', 'limit' => 5); $this->assertEquals($expected, $result['named']); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'params_pass'), array('named' => array('sort' => 'desc', 'limit' => 5)) ); $this->assertEquals($expected, $result['named']); } /** * Test that requestAction handles get parameters correctly. * * @return void */ public function testRequestActionGetParameters() { $result = $this->object->requestAction( '/request_action/params_pass?get=value&limit=5' ); $this->assertEquals('value', $result->query['get']); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'params_pass'), array('url' => array('get' => 'value', 'limit' => 5)) ); $this->assertEquals('value', $result->query['get']); } /** * test that requestAction does not fish data out of the POST * superglobal. * * @return void */ public function testRequestActionNoPostPassing() { $_tmp = $_POST; $_POST = array('data' => array( 'item' => 'value' )); $result = $this->object->requestAction(array('controller' => 'request_action', 'action' => 'post_pass')); $this->assertEmpty($result); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'post_pass'), array('data' => $_POST['data']) ); $expected = $_POST['data']; $this->assertEquals($expected, $result); $result = $this->object->requestAction('/request_action/post_pass'); $expected = $_POST['data']; $this->assertEquals($expected, $result); $_POST = $_tmp; } /** * Test requestAction with post data. * * @return void */ public function testRequestActionPostWithData() { $data = array( 'Post' => array('id' => 2) ); $result = $this->object->requestAction( array('controller' => 'request_action', 'action' => 'post_pass'), array('data' => $data) ); $this->assertEquals($data, $result); $result = $this->object->requestAction( '/request_action/post_pass', array('data' => $data) ); $this->assertEquals($data, $result); } /** * Test backward compatibility * * @return voind */ public function testBackwardCompatibility() { $this->skipIf(version_compare(PHP_VERSION, '7.0.0', '>=')); $this->assertInstanceOf('Object', new ObjectTestModel); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Core/ConfigureTest.php0000644000000000000000000003441313365153155023276 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Core * @since CakePHP(tm) v 1.2.0.5432 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('PhpReader', 'Configure'); /** * ConfigureTest * * @package Cake.Test.Case.Core */ class ConfigureTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); Configure::write('Cache.disable', true); App::build(); App::objects('plugin', null, true); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_core_paths')) { unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_core_paths'); } if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_dir_map')) { unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_dir_map'); } if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_file_map')) { unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_file_map'); } if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_object_map')) { unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_object_map'); } if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'test.config.php')) { unlink(TMP . 'cache' . DS . 'persistent' . DS . 'test.config.php'); } if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'test.php')) { unlink(TMP . 'cache' . DS . 'persistent' . DS . 'test.php'); } Configure::drop('test'); } /** * Test to ensure bootrapping doesn't overwrite prior configs set under 'App' key * * @return void */ public function testBootstrap() { $expected = array( 'foo' => 'bar' ); Configure::write('App', $expected); Configure::bootstrap(true); $result = Configure::read('App'); $this->assertEquals($expected['foo'], $result['foo']); $this->assertFalse($result['base']); } /** * testRead method * * @return void */ public function testRead() { $expected = 'ok'; Configure::write('level1.level2.level3_1', $expected); Configure::write('level1.level2.level3_2', 'something_else'); $result = Configure::read('level1.level2.level3_1'); $this->assertEquals($expected, $result); $result = Configure::read('level1.level2.level3_2'); $this->assertEquals('something_else', $result); $result = Configure::read('debug'); $this->assertTrue($result >= 0); $result = Configure::read(); $this->assertTrue(is_array($result)); $this->assertTrue(isset($result['debug'])); $this->assertTrue(isset($result['level1'])); $result = Configure::read('something_I_just_made_up_now'); $this->assertEquals(null, $result, 'Missing key should return null.'); } /** * testWrite method * * @return void */ public function testWrite() { $writeResult = Configure::write('SomeName.someKey', 'myvalue'); $this->assertTrue($writeResult); $result = Configure::read('SomeName.someKey'); $this->assertEquals('myvalue', $result); $writeResult = Configure::write('SomeName.someKey', null); $this->assertTrue($writeResult); $result = Configure::read('SomeName.someKey'); $this->assertEquals(null, $result); $expected = array('One' => array('Two' => array('Three' => array('Four' => array('Five' => 'cool'))))); $writeResult = Configure::write('Key', $expected); $this->assertTrue($writeResult); $result = Configure::read('Key'); $this->assertEquals($expected, $result); $result = Configure::read('Key.One'); $this->assertEquals($expected['One'], $result); $result = Configure::read('Key.One.Two'); $this->assertEquals($expected['One']['Two'], $result); $result = Configure::read('Key.One.Two.Three.Four.Five'); $this->assertEquals('cool', $result); Configure::write('one.two.three.four', '4'); $result = Configure::read('one.two.three.four'); $this->assertEquals('4', $result); } /** * Test the consume method. * * @return void */ public function testConsume() { $this->assertNull(Configure::consume('DoesNotExist'), 'Should be null on empty value'); Configure::write('Test', array('key' => 'value', 'key2' => 'value2')); $result = Configure::consume('Test.key'); $this->assertEquals('value', $result); $result = Configure::read('Test.key2'); $this->assertEquals('value2', $result, 'Other values should remain.'); $result = Configure::consume('Test'); $expected = array('key2' => 'value2'); $this->assertEquals($expected, $result); } /** * testConsumeEmpty * * @return void */ public function testConsumeEmpty() { Configure::write('Test', array('key' => 'value', 'key2' => 'value2')); $result = Configure::consume(''); $this->assertNull($result); $result = Configure::consume(null); $this->assertNull($result); } /** * test setting display_errors with debug. * * @return void */ public function testDebugSettingDisplayErrors() { Configure::write('debug', 0); $result = ini_get('display_errors'); $this->assertEquals(0, $result); Configure::write('debug', 2); $result = ini_get('display_errors'); $this->assertEquals(1, $result); } /** * testDelete method * * @return void */ public function testDelete() { Configure::write('SomeName.someKey', 'myvalue'); $result = Configure::read('SomeName.someKey'); $this->assertEquals('myvalue', $result); Configure::delete('SomeName.someKey'); $result = Configure::read('SomeName.someKey'); $this->assertNull($result); Configure::write('SomeName', array('someKey' => 'myvalue', 'otherKey' => 'otherValue')); $result = Configure::read('SomeName.someKey'); $this->assertEquals('myvalue', $result); $result = Configure::read('SomeName.otherKey'); $this->assertEquals('otherValue', $result); Configure::delete('SomeName'); $result = Configure::read('SomeName.someKey'); $this->assertNull($result); $result = Configure::read('SomeName.otherKey'); $this->assertNull($result); } /** * testCheck method * * @return void */ public function testCheck() { Configure::write('ConfigureTestCase', 'value'); $this->assertTrue(Configure::check('ConfigureTestCase')); $this->assertFalse(Configure::check('NotExistingConfigureTestCase')); } /** * testCheckingSavedEmpty method * * @return void */ public function testCheckingSavedEmpty() { $this->assertTrue(Configure::write('ConfigureTestCase', 0)); $this->assertTrue(Configure::check('ConfigureTestCase')); $this->assertTrue(Configure::write('ConfigureTestCase', '0')); $this->assertTrue(Configure::check('ConfigureTestCase')); $this->assertTrue(Configure::write('ConfigureTestCase', false)); $this->assertTrue(Configure::check('ConfigureTestCase')); $this->assertTrue(Configure::write('ConfigureTestCase', null)); $this->assertFalse(Configure::check('ConfigureTestCase')); } /** * testCheckKeyWithSpaces method * * @return void */ public function testCheckKeyWithSpaces() { $this->assertTrue(Configure::write('Configure Test', "test")); $this->assertTrue(Configure::check('Configure Test')); Configure::delete('Configure Test'); $this->assertTrue(Configure::write('Configure Test.Test Case', "test")); $this->assertTrue(Configure::check('Configure Test.Test Case')); } /** * testCheckEmpty * * @return void */ public function testCheckEmpty() { $this->assertFalse(Configure::check('')); $this->assertFalse(Configure::check(null)); } /** * testLoad method * * @expectedException RuntimeException * @return void */ public function testLoadExceptionOnNonExistantFile() { Configure::config('test', new PhpReader()); Configure::load('non_existing_configuration_file', 'test'); } /** * test load method for default config creation * * @return void */ public function testLoadDefaultConfig() { try { Configure::load('non_existing_configuration_file'); } catch (Exception $e) { $result = Configure::configured('default'); $this->assertTrue($result); } } /** * test load with merging * * @return void */ public function testLoadWithMerge() { Configure::config('test', new PhpReader(CAKE . 'Test' . DS . 'test_app' . DS . 'Config' . DS)); $result = Configure::load('var_test', 'test'); $this->assertTrue($result); $this->assertEquals('value', Configure::read('Read')); $result = Configure::load('var_test2', 'test', true); $this->assertTrue($result); $this->assertEquals('value2', Configure::read('Read')); $this->assertEquals('buried2', Configure::read('Deep.Second.SecondDeepest')); $this->assertEquals('buried', Configure::read('Deep.Deeper.Deepest')); $this->assertEquals('Overwrite', Configure::read('TestAcl.classname')); $this->assertEquals('one', Configure::read('TestAcl.custom')); } /** * test loading with overwrite * * @return void */ public function testLoadNoMerge() { Configure::config('test', new PhpReader(CAKE . 'Test' . DS . 'test_app' . DS . 'Config' . DS)); $result = Configure::load('var_test', 'test'); $this->assertTrue($result); $this->assertEquals('value', Configure::read('Read')); $result = Configure::load('var_test2', 'test', false); $this->assertTrue($result); $this->assertEquals('value2', Configure::read('Read')); $this->assertEquals('buried2', Configure::read('Deep.Second.SecondDeepest')); $this->assertNull(Configure::read('Deep.Deeper.Deepest')); } /** * testLoad method * * @return void */ public function testLoadPlugin() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); Configure::config('test', new PhpReader()); CakePlugin::load('TestPlugin'); $result = Configure::load('TestPlugin.load', 'test'); $this->assertTrue($result); $expected = '/test_app/plugins/test_plugin/config/load.php'; $config = Configure::read('plugin_load'); $this->assertEquals($expected, $config); $result = Configure::load('TestPlugin.more.load', 'test'); $this->assertTrue($result); $expected = '/test_app/plugins/test_plugin/config/more.load.php'; $config = Configure::read('plugin_more_load'); $this->assertEquals($expected, $config); CakePlugin::unload(); } /** * testStore method * * @return void */ public function testStoreAndRestore() { Configure::write('Cache.disable', false); Configure::write('Testing', 'yummy'); $this->assertTrue(Configure::store('store_test', 'default')); Configure::delete('Testing'); $this->assertNull(Configure::read('Testing')); Configure::restore('store_test', 'default'); $this->assertEquals('yummy', Configure::read('Testing')); Cache::delete('store_test', 'default'); } /** * test that store and restore only store/restore the provided data. * * @return void */ public function testStoreAndRestoreWithData() { Configure::write('Cache.disable', false); Configure::write('testing', 'value'); Configure::store('store_test', 'default', array('store_test' => 'one')); Configure::delete('testing'); $this->assertNull(Configure::read('store_test'), 'Calling store with data shouldn\'t modify runtime.'); Configure::restore('store_test', 'default'); $this->assertEquals('one', Configure::read('store_test')); $this->assertNull(Configure::read('testing'), 'Values that were not stored are not restored.'); Cache::delete('store_test', 'default'); } /** * testVersion method * * @return void */ public function testVersion() { $result = Configure::version(); $this->assertTrue(version_compare($result, '1.2', '>=')); } /** * test adding new readers. * * @return void */ public function testReaderSetup() { $reader = new PhpReader(); Configure::config('test', $reader); $configured = Configure::configured(); $this->assertTrue(in_array('test', $configured)); $this->assertTrue(Configure::configured('test')); $this->assertFalse(Configure::configured('fake_garbage')); $this->assertTrue(Configure::drop('test')); $this->assertFalse(Configure::drop('test'), 'dropping things that do not exist should return false.'); } /** * test reader() throwing exceptions on missing interface. * * @expectedException PHPUnit_Framework_Error * @return void * @throws PHPUnit_Framework_Error */ public function testReaderExceptionOnIncorrectClass() { $reader = new StdClass(); try { Configure::config('test', $reader); } catch (TypeError $e) { throw new PHPUnit_Framework_Error('Raised an error', 100, __FILE__, __LINE__); } } /** * Test that clear wipes all values. * * @return void */ public function testClear() { Configure::write('test', 'value'); $this->assertTrue(Configure::clear()); $this->assertNull(Configure::read('debug')); $this->assertNull(Configure::read('test')); } /** * testDumpNoAdapter * * @expectedException ConfigureException * @return void */ public function testDumpNoAdapter() { Configure::dump(TMP . 'test.php', 'does_not_exist'); } /** * test dump integrated with the PhpReader. * * @return void */ public function testDump() { Configure::config('test_reader', new PhpReader(TMP)); $result = Configure::dump('config_test.php', 'test_reader'); $this->assertTrue($result > 0); $result = file_get_contents(TMP . 'config_test.php'); $this->assertContains('assertContains('$config = ', $result); if (file_exists(TMP . 'config_test.php')) { unlink(TMP . 'config_test.php'); } } /** * Test dumping only some of the data. * * @return void */ public function testDumpPartial() { Configure::config('test_reader', new PhpReader(TMP)); $result = Configure::dump('config_test.php', 'test_reader', array('Error')); $this->assertTrue($result > 0); $result = file_get_contents(TMP . 'config_test.php'); $this->assertContains('assertContains('$config = ', $result); $this->assertContains('Error', $result); $this->assertNotContains('debug', $result); if (file_exists(TMP . 'config_test.php')) { unlink(TMP . 'config_test.php'); } } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Core/CakePluginTest.php0000644000000000000000000002301013365153155023366 0ustar rootroot array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); App::objects('plugins', null, false); } /** * Reverts the changes done to the environment while testing * * @return void */ public function tearDown() { parent::tearDown(); CakePlugin::unload(); } /** * Tests loading a single plugin * * @return void */ public function testLoadSingle() { CakePlugin::unload(); CakePlugin::load('TestPlugin'); $expected = array('TestPlugin'); $this->assertEquals($expected, CakePlugin::loaded()); } /** * Tests unloading plugins * * @return void */ public function testUnload() { CakePlugin::load('TestPlugin'); $expected = array('TestPlugin'); $this->assertEquals($expected, CakePlugin::loaded()); CakePlugin::unload('TestPlugin'); $this->assertEquals(array(), CakePlugin::loaded()); CakePlugin::load('TestPlugin'); $expected = array('TestPlugin'); $this->assertEquals($expected, CakePlugin::loaded()); CakePlugin::unload('TestFakePlugin'); $this->assertEquals($expected, CakePlugin::loaded()); } /** * Tests loading a plugin and its bootstrap file * * @return void */ public function testLoadSingleWithBootstrap() { CakePlugin::load('TestPlugin', array('bootstrap' => true)); $this->assertTrue(CakePlugin::loaded('TestPlugin')); $this->assertEquals('loaded plugin bootstrap', Configure::read('CakePluginTest.test_plugin.bootstrap')); } /** * Tests loading a plugin with bootstrap file and routes file * * @return void */ public function testLoadSingleWithBootstrapAndRoutes() { CakePlugin::load('TestPlugin', array('bootstrap' => true, 'routes' => true)); $this->assertTrue(CakePlugin::loaded('TestPlugin')); $this->assertEquals('loaded plugin bootstrap', Configure::read('CakePluginTest.test_plugin.bootstrap')); CakePlugin::routes(); $this->assertEquals('loaded plugin routes', Configure::read('CakePluginTest.test_plugin.routes')); } /** * Tests loading multiple plugins at once * * @return void */ public function testLoadMultiple() { CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $expected = array('TestPlugin', 'TestPluginTwo'); $this->assertEquals($expected, CakePlugin::loaded()); } /** * Tests loading multiple plugins and their bootstrap files * * @return void */ public function testLoadMultipleWithDefaults() { CakePlugin::load(array('TestPlugin', 'TestPluginTwo'), array('bootstrap' => true, 'routes' => false)); $expected = array('TestPlugin', 'TestPluginTwo'); $this->assertEquals($expected, CakePlugin::loaded()); $this->assertEquals('loaded plugin bootstrap', Configure::read('CakePluginTest.test_plugin.bootstrap')); $this->assertEquals('loaded plugin two bootstrap', Configure::read('CakePluginTest.test_plugin_two.bootstrap')); } /** * Tests loading multiple plugins with default loading params and some overrides * * @return void */ public function testLoadMultipleWithDefaultsAndOverride() { CakePlugin::load( array('TestPlugin', 'TestPluginTwo' => array('routes' => false)), array('bootstrap' => true, 'routes' => true) ); $expected = array('TestPlugin', 'TestPluginTwo'); $this->assertEquals($expected, CakePlugin::loaded()); $this->assertEquals('loaded plugin bootstrap', Configure::read('CakePluginTest.test_plugin.bootstrap')); $this->assertEquals(null, Configure::read('CakePluginTest.test_plugin_two.bootstrap')); } /** * Tests that it is possible to load multiple bootstrap files at once * * @return void */ public function testMultipleBootstrapFiles() { CakePlugin::load('TestPlugin', array('bootstrap' => array('bootstrap', 'custom_config'))); $this->assertTrue(CakePlugin::loaded('TestPlugin')); $this->assertEquals('loaded plugin bootstrap', Configure::read('CakePluginTest.test_plugin.bootstrap')); } /** * Tests that it is possible to load plugin bootstrap by calling a callback function * * @return void */ public function testCallbackBootstrap() { CakePlugin::load('TestPlugin', array('bootstrap' => array($this, 'pluginBootstrap'))); $this->assertTrue(CakePlugin::loaded('TestPlugin')); $this->assertEquals('called plugin bootstrap callback', Configure::read('CakePluginTest.test_plugin.bootstrap')); } /** * Tests that loading a missing routes file throws a warning * * @return void * @expectedException PHPUNIT_FRAMEWORK_ERROR_WARNING */ public function testLoadMultipleWithDefaultsMissingFile() { CakePlugin::load(array('TestPlugin', 'TestPluginTwo'), array('bootstrap' => true, 'routes' => true)); CakePlugin::routes(); } /** * Test ignoring missing bootstrap/routes file * * @return void */ public function testIgnoreMissingFiles() { CakePlugin::loadAll(array(array( 'bootstrap' => true, 'routes' => true, 'ignoreMissing' => true ))); CakePlugin::routes(); } /** * Tests that CakePlugin::load() throws an exception on unknown plugin * * @return void * @expectedException MissingPluginException */ public function testLoadNotFound() { CakePlugin::load('MissingPlugin'); } /** * Tests that CakePlugin::path() returns the correct path for the loaded plugins * * @return void */ public function testPath() { CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS . 'TestPlugin' . DS; $this->assertEquals($expected, CakePlugin::path('TestPlugin')); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS . 'TestPluginTwo' . DS; $this->assertEquals($expected, CakePlugin::path('TestPluginTwo')); } /** * Tests that CakePlugin::path() throws an exception on unknown plugin * * @return void * @expectedException MissingPluginException */ public function testPathNotFound() { CakePlugin::path('TestPlugin'); } /** * Tests that CakePlugin::loadAll() will load all plugins in the configured folder * * @return void */ public function testLoadAll() { CakePlugin::loadAll(); $expected = array('PluginJs', 'TestPlugin', 'TestPluginTwo'); $this->assertEquals($expected, CakePlugin::loaded()); } /** * Tests that CakePlugin::loadAll() will load all plugins in the configured folder with bootstrap loading * * @return void */ public function testLoadAllWithDefaults() { $defaults = array('bootstrap' => true); CakePlugin::loadAll(array($defaults)); $expected = array('PluginJs', 'TestPlugin', 'TestPluginTwo'); $this->assertEquals($expected, CakePlugin::loaded()); $this->assertEquals('loaded js plugin bootstrap', Configure::read('CakePluginTest.js_plugin.bootstrap')); $this->assertEquals('loaded plugin bootstrap', Configure::read('CakePluginTest.test_plugin.bootstrap')); $this->assertEquals('loaded plugin two bootstrap', Configure::read('CakePluginTest.test_plugin_two.bootstrap')); } /** * Tests that CakePlugin::loadAll() will load all plugins in the configured folder with defaults * and merges in global defaults. * * @return void */ public function testLoadAllWithDefaultsAndOverride() { CakePlugin::loadAll(array(array('bootstrap' => true), 'TestPlugin' => array('routes' => true))); CakePlugin::routes(); $expected = array('PluginJs', 'TestPlugin', 'TestPluginTwo'); $this->assertEquals($expected, CakePlugin::loaded()); $this->assertEquals('loaded js plugin bootstrap', Configure::read('CakePluginTest.js_plugin.bootstrap')); $this->assertEquals('loaded plugin routes', Configure::read('CakePluginTest.test_plugin.routes')); $this->assertEquals('loaded plugin bootstrap', Configure::read('CakePluginTest.test_plugin.bootstrap')); $this->assertEquals('loaded plugin two bootstrap', Configure::read('CakePluginTest.test_plugin_two.bootstrap')); } /** * Tests that CakePlugin::loadAll() will load all plugins in the configured folder with defaults * and overrides for a plugin * * @return void */ public function testLoadAllWithDefaultsAndOverrideComplex() { CakePlugin::loadAll(array(array('bootstrap' => true), 'TestPlugin' => array('routes' => true, 'bootstrap' => false))); CakePlugin::routes(); $expected = array('PluginJs', 'TestPlugin', 'TestPluginTwo'); $this->assertEquals($expected, CakePlugin::loaded()); $this->assertEquals('loaded js plugin bootstrap', Configure::read('CakePluginTest.js_plugin.bootstrap')); $this->assertEquals('loaded plugin routes', Configure::read('CakePluginTest.test_plugin.routes')); $this->assertEquals(null, Configure::read('CakePluginTest.test_plugin.bootstrap')); $this->assertEquals('loaded plugin two bootstrap', Configure::read('CakePluginTest.test_plugin_two.bootstrap')); } /** * Auxiliary function to test plugin bootstrap callbacks * * @return void */ public function pluginBootstrap() { Configure::write('CakePluginTest.test_plugin.bootstrap', 'called plugin bootstrap callback'); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/0000755000000000000000000000000013365153155020112 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/0000755000000000000000000000000013365153155021317 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/FileEngineTest.php0000644000000000000000000004657013365153155024711 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine * @since CakePHP(tm) v 1.2.0.5434 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); /** * FileEngineTest class * * @package Cake.Test.Case.Cache.Engine */ class FileEngineTest extends CakeTestCase { /** * config property * * @var array */ public $config = array(); /** * setUp method * * @return void */ public function setUp() { parent::setUp(); Configure::write('Cache.disable', false); Cache::config('file_test', array('engine' => 'File', 'path' => CACHE)); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); // Cache::clear(false, 'file_test'); Cache::drop('file_test'); Cache::drop('file_groups'); Cache::drop('file_groups2'); Cache::drop('file_groups3'); } /** * testCacheDirChange method * * @return void */ public function testCacheDirChange() { $result = Cache::config('sessions', array('engine' => 'File', 'path' => TMP . 'sessions')); $this->assertEquals(Cache::settings('sessions'), $result['settings']); $result = Cache::config('sessions', array('engine' => 'File', 'path' => TMP . 'tests')); $this->assertEquals(Cache::settings('sessions'), $result['settings']); $this->assertNotEquals(Cache::settings('default'), $result['settings']); } /** * testReadAndWriteCache method * * @return void */ public function testReadAndWriteCache() { Cache::config('default'); $result = Cache::write(null, 'here', 'file_test'); $this->assertFalse($result); Cache::set(array('duration' => 1), 'file_test'); $result = Cache::read('test', 'file_test'); $expecting = ''; $this->assertEquals($expecting, $result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('test', $data, 'file_test'); $this->assertTrue(file_exists(CACHE . 'cake_test')); $result = Cache::read('test', 'file_test'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::delete('test', 'file_test'); } /** * Test read/write on the same cache key. Ensures file handles are re-wound. * * @return void */ public function testConsecutiveReadWrite() { Cache::write('rw', 'first write', 'file_test'); $result = Cache::read('rw', 'file_test'); Cache::write('rw', 'second write', 'file_test'); $resultB = Cache::read('rw', 'file_test'); Cache::delete('rw', 'file_test'); $this->assertEquals('first write', $result); $this->assertEquals('second write', $resultB); } /** * testExpiry method * * @return void */ public function testExpiry() { Cache::set(array('duration' => 1), 'file_test'); $result = Cache::read('test', 'file_test'); $this->assertFalse($result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'file_test'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'file_test'); $this->assertFalse($result); Cache::set(array('duration' => "+1 second"), 'file_test'); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'file_test'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'file_test'); $this->assertFalse($result); } /** * testDeleteCache method * * @return void */ public function testDeleteCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('delete_test', $data, 'file_test'); $this->assertTrue($result); $result = Cache::delete('delete_test', 'file_test'); $this->assertTrue($result); $this->assertFalse(file_exists(TMP . 'tests' . DS . 'delete_test')); $result = Cache::delete('delete_test', 'file_test'); $this->assertFalse($result); } /** * testSerialize method * * @return void */ public function testSerialize() { Cache::config('file_test', array('engine' => 'File', 'serialize' => true)); $data = 'this is a test of the emergency broadcasting system'; $write = Cache::write('serialize_test', $data, 'file_test'); $this->assertTrue($write); Cache::config('file_test', array('serialize' => false)); $read = Cache::read('serialize_test', 'file_test'); $newread = Cache::read('serialize_test', 'file_test'); Cache::delete('serialize_test', 'file_test'); $this->assertSame($read, serialize($data)); $this->assertSame(unserialize($newread), $data); } /** * testClear method * * @return void */ public function testClear() { Cache::config('file_test', array('engine' => 'File', 'duration' => 1)); $data = 'this is a test of the emergency broadcasting system'; Cache::write('serialize_test1', $data, 'file_test'); Cache::write('serialize_test2', $data, 'file_test'); Cache::write('serialize_test3', $data, 'file_test'); $this->assertTrue(file_exists(CACHE . 'cake_serialize_test1')); $this->assertTrue(file_exists(CACHE . 'cake_serialize_test2')); $this->assertTrue(file_exists(CACHE . 'cake_serialize_test3')); sleep(2); $result = Cache::clear(true, 'file_test'); $this->assertTrue($result); $this->assertFalse(file_exists(CACHE . 'cake_serialize_test1')); $this->assertFalse(file_exists(CACHE . 'cake_serialize_test2')); $this->assertFalse(file_exists(CACHE . 'cake_serialize_test3')); $data = 'this is a test of the emergency broadcasting system'; Cache::write('serialize_test1', $data, 'file_test'); Cache::write('serialize_test2', $data, 'file_test'); Cache::write('serialize_test3', $data, 'file_test'); $this->assertTrue(file_exists(CACHE . 'cake_serialize_test1')); $this->assertTrue(file_exists(CACHE . 'cake_serialize_test2')); $this->assertTrue(file_exists(CACHE . 'cake_serialize_test3')); $result = Cache::clear(false, 'file_test'); $this->assertTrue($result); $this->assertFalse(file_exists(CACHE . 'cake_serialize_test1')); $this->assertFalse(file_exists(CACHE . 'cake_serialize_test2')); $this->assertFalse(file_exists(CACHE . 'cake_serialize_test3')); } /** * test that clear() doesn't wipe files not in the current engine's prefix. * * @return void */ public function testClearWithPrefixes() { $FileOne = new FileEngine(); $FileOne->init(array( 'prefix' => 'prefix_one_', 'duration' => DAY )); $FileTwo = new FileEngine(); $FileTwo->init(array( 'prefix' => 'prefix_two_', 'duration' => DAY )); $dataOne = $dataTwo = $expected = 'content to cache'; $FileOne->write('prefix_one_key_one', $dataOne, DAY); $FileTwo->write('prefix_two_key_two', $dataTwo, DAY); $this->assertEquals($expected, $FileOne->read('prefix_one_key_one')); $this->assertEquals($expected, $FileTwo->read('prefix_two_key_two')); $FileOne->clear(false); $this->assertEquals($expected, $FileTwo->read('prefix_two_key_two'), 'secondary config was cleared by accident.'); $FileTwo->clear(false); } /** * Test that clear() also removes files with group tags. * * @return void */ public function testClearWithGroups() { $engine = new FileEngine(); $engine->init(array( 'prefix' => 'cake_test_', 'duration' => DAY, 'groups' => array('short', 'round') )); $key = 'cake_test_test_key'; $engine->write($key, 'it works', DAY); $engine->clear(false); $this->assertFalse($engine->read($key), 'Key should have been removed'); } /** * Test that clear() also removes files with group tags. * * @return void */ public function testClearWithNoKeys() { $engine = new FileEngine(); $engine->init(array( 'prefix' => 'cake_test_', 'duration' => DAY, 'groups' => array('one', 'two') )); $key = 'cake_test_test_key'; $engine->clear(false); $this->assertFalse($engine->read($key), 'No errors should be found'); } /** * testKeyPath method * * @return void */ public function testKeyPath() { $result = Cache::write('views.countries.something', 'here', 'file_test'); $this->assertTrue($result); $this->assertTrue(file_exists(CACHE . 'cake_views_countries_something')); $result = Cache::read('views.countries.something', 'file_test'); $this->assertEquals('here', $result); $result = Cache::clear(false, 'file_test'); $this->assertTrue($result); $result = Cache::write('domain.test.com:8080', 'here', 'file_test'); $this->assertTrue($result); $this->assertTrue(file_exists(CACHE . 'cake_domain_test_com_8080')); $result = Cache::write('command>dir|more', 'here', 'file_test'); $this->assertTrue($result); $this->assertTrue(file_exists(CACHE . 'cake_command_dir_more')); } /** * testRemoveWindowsSlashesFromCache method * * @return void */ public function testRemoveWindowsSlashesFromCache() { Cache::config('windows_test', array('engine' => 'File', 'isWindows' => true, 'prefix' => null, 'path' => TMP)); $expected = array( 'C:\dev\prj2\sites\cake\libs' => array( 0 => 'C:\dev\prj2\sites\cake\libs', 1 => 'C:\dev\prj2\sites\cake\libs\view', 2 => 'C:\dev\prj2\sites\cake\libs\view\scaffolds', 3 => 'C:\dev\prj2\sites\cake\libs\view\pages', 4 => 'C:\dev\prj2\sites\cake\libs\view\layouts', 5 => 'C:\dev\prj2\sites\cake\libs\view\layouts\xml', 6 => 'C:\dev\prj2\sites\cake\libs\view\layouts\rss', 7 => 'C:\dev\prj2\sites\cake\libs\view\layouts\js', 8 => 'C:\dev\prj2\sites\cake\libs\view\layouts\email', 9 => 'C:\dev\prj2\sites\cake\libs\view\layouts\email\text', 10 => 'C:\dev\prj2\sites\cake\libs\view\layouts\email\html', 11 => 'C:\dev\prj2\sites\cake\libs\view\helpers', 12 => 'C:\dev\prj2\sites\cake\libs\view\errors', 13 => 'C:\dev\prj2\sites\cake\libs\view\elements', 14 => 'C:\dev\prj2\sites\cake\libs\view\elements\email', 15 => 'C:\dev\prj2\sites\cake\libs\view\elements\email\text', 16 => 'C:\dev\prj2\sites\cake\libs\view\elements\email\html', 17 => 'C:\dev\prj2\sites\cake\libs\model', 18 => 'C:\dev\prj2\sites\cake\libs\model\datasources', 19 => 'C:\dev\prj2\sites\cake\libs\model\datasources\dbo', 20 => 'C:\dev\prj2\sites\cake\libs\model\behaviors', 21 => 'C:\dev\prj2\sites\cake\libs\controller', 22 => 'C:\dev\prj2\sites\cake\libs\controller\components', 23 => 'C:\dev\prj2\sites\cake\libs\cache'), 'C:\dev\prj2\sites\main_site\vendors' => array( 0 => 'C:\dev\prj2\sites\main_site\vendors', 1 => 'C:\dev\prj2\sites\main_site\vendors\shells', 2 => 'C:\dev\prj2\sites\main_site\vendors\shells\templates', 3 => 'C:\dev\prj2\sites\main_site\vendors\shells\templates\cdc_project', 4 => 'C:\dev\prj2\sites\main_site\vendors\shells\tasks', 5 => 'C:\dev\prj2\sites\main_site\vendors\js', 6 => 'C:\dev\prj2\sites\main_site\vendors\css'), 'C:\dev\prj2\sites\vendors' => array( 0 => 'C:\dev\prj2\sites\vendors', 1 => 'C:\dev\prj2\sites\vendors\simpletest', 2 => 'C:\dev\prj2\sites\vendors\simpletest\test', 3 => 'C:\dev\prj2\sites\vendors\simpletest\test\support', 4 => 'C:\dev\prj2\sites\vendors\simpletest\test\support\collector', 5 => 'C:\dev\prj2\sites\vendors\simpletest\extensions', 6 => 'C:\dev\prj2\sites\vendors\simpletest\extensions\testdox', 7 => 'C:\dev\prj2\sites\vendors\simpletest\docs', 8 => 'C:\dev\prj2\sites\vendors\simpletest\docs\fr', 9 => 'C:\dev\prj2\sites\vendors\simpletest\docs\en'), 'C:\dev\prj2\sites\main_site\views\helpers' => array( 0 => 'C:\dev\prj2\sites\main_site\views\helpers') ); Cache::write('test_dir_map', $expected, 'windows_test'); $data = Cache::read('test_dir_map', 'windows_test'); Cache::delete('test_dir_map', 'windows_test'); $this->assertEquals($expected, $data); Cache::drop('windows_test'); } /** * testWriteQuotedString method * * @return void */ public function testWriteQuotedString() { Cache::config('file_test', array('engine' => 'File', 'path' => TMP . 'tests')); Cache::write('App.doubleQuoteTest', '"this is a quoted string"', 'file_test'); $this->assertSame(Cache::read('App.doubleQuoteTest', 'file_test'), '"this is a quoted string"'); Cache::write('App.singleQuoteTest', "'this is a quoted string'", 'file_test'); $this->assertSame(Cache::read('App.singleQuoteTest', 'file_test'), "'this is a quoted string'"); Cache::config('file_test', array('isWindows' => true, 'path' => TMP . 'tests')); $this->assertSame(Cache::read('App.doubleQuoteTest', 'file_test'), '"this is a quoted string"'); Cache::write('App.singleQuoteTest', "'this is a quoted string'", 'file_test'); $this->assertSame(Cache::read('App.singleQuoteTest', 'file_test'), "'this is a quoted string'"); Cache::delete('App.singleQuoteTest', 'file_test'); Cache::delete('App.doubleQuoteTest', 'file_test'); } /** * check that FileEngine does not generate an error when a configured Path does not exist in debug mode. * * @return void */ public function testPathDoesNotExist() { $this->skipIf(is_dir(TMP . 'tests' . DS . 'autocreate'), 'Cannot run if test directory exists.'); Cache::config('autocreate', array( 'engine' => 'File', 'path' => TMP . 'tests' . DS . 'autocreate' )); Cache::drop('autocreate'); } /** * Testing the mask setting in FileEngine * * @return void */ public function testMaskSetting() { if (DS === '\\') { $this->markTestSkipped('File permission testing does not work on Windows.'); } Cache::config('mask_test', array('engine' => 'File', 'path' => TMP . 'tests')); $data = 'This is some test content'; $write = Cache::write('masking_test', $data, 'mask_test'); $result = substr(sprintf('%o', fileperms(TMP . 'tests' . DS . 'cake_masking_test')), -4); $expected = '0664'; $this->assertEquals($expected, $result); Cache::delete('masking_test', 'mask_test'); Cache::drop('mask_test'); Cache::config('mask_test', array('engine' => 'File', 'mask' => 0666, 'path' => TMP . 'tests')); Cache::write('masking_test', $data, 'mask_test'); $result = substr(sprintf('%o', fileperms(TMP . 'tests' . DS . 'cake_masking_test')), -4); $expected = '0666'; $this->assertEquals($expected, $result); Cache::delete('masking_test', 'mask_test'); Cache::drop('mask_test'); Cache::config('mask_test', array('engine' => 'File', 'mask' => 0644, 'path' => TMP . 'tests')); Cache::write('masking_test', $data, 'mask_test'); $result = substr(sprintf('%o', fileperms(TMP . 'tests' . DS . 'cake_masking_test')), -4); $expected = '0644'; $this->assertEquals($expected, $result); Cache::delete('masking_test', 'mask_test'); Cache::drop('mask_test'); Cache::config('mask_test', array('engine' => 'File', 'mask' => 0640, 'path' => TMP . 'tests')); Cache::write('masking_test', $data, 'mask_test'); $result = substr(sprintf('%o', fileperms(TMP . 'tests' . DS . 'cake_masking_test')), -4); $expected = '0640'; $this->assertEquals($expected, $result); Cache::delete('masking_test', 'mask_test'); Cache::drop('mask_test'); } /** * Tests that configuring groups for stored keys return the correct values when read/written * * @return void */ public function testGroupsReadWrite() { Cache::config('file_groups', array('engine' => 'File', 'duration' => 3600, 'groups' => array('group_a', 'group_b'))); $this->assertTrue(Cache::write('test_groups', 'value', 'file_groups')); $this->assertEquals('value', Cache::read('test_groups', 'file_groups')); $this->assertTrue(Cache::write('test_groups2', 'value2', 'file_groups')); $this->assertTrue(Cache::write('test_groups3', 'value3', 'file_groups')); } /** * Test that clearing with repeat writes works properly * * @return void */ public function testClearingWithRepeatWrites() { Cache::config('repeat', array( 'engine' => 'File', 'groups' => array('users') )); $this->assertTrue(Cache::write('user', 'rchavik', 'repeat')); $this->assertEquals('rchavik', Cache::read('user', 'repeat')); Cache::delete('user', 'repeat'); $this->assertEquals(false, Cache::read('user', 'repeat')); $this->assertTrue(Cache::write('user', 'ADmad', 'repeat')); $this->assertEquals('ADmad', Cache::read('user', 'repeat')); Cache::clearGroup('users', 'repeat'); $this->assertEquals(false, Cache::read('user', 'repeat')); $this->assertTrue(Cache::write('user', 'markstory', 'repeat')); $this->assertEquals('markstory', Cache::read('user', 'repeat')); Cache::drop('repeat'); } /** * Tests that deleting from a groups-enabled config is possible * * @return void */ public function testGroupDelete() { Cache::config('file_groups', array( 'engine' => 'File', 'duration' => 3600, 'groups' => array('group_a', 'group_b') )); $this->assertTrue(Cache::write('test_groups', 'value', 'file_groups')); $this->assertEquals('value', Cache::read('test_groups', 'file_groups')); $this->assertTrue(Cache::delete('test_groups', 'file_groups')); $this->assertFalse(Cache::read('test_groups', 'file_groups')); } /** * Test clearing a cache group * * @return void */ public function testGroupClear() { Cache::config('file_groups', array('engine' => 'File', 'duration' => 3600, 'groups' => array('group_a', 'group_b'))); Cache::config('file_groups2', array('engine' => 'File', 'duration' => 3600, 'groups' => array('group_b'))); Cache::config('file_groups3', array( 'engine' => 'File', 'duration' => 3600, 'groups' => array('group_b'), 'prefix' => 'leading_', )); $this->assertTrue(Cache::write('test_groups', 'value', 'file_groups')); $this->assertTrue(Cache::write('test_groups2', 'value 2', 'file_groups2')); $this->assertTrue(Cache::write('test_groups3', 'value 3', 'file_groups3')); $this->assertTrue(Cache::clearGroup('group_b', 'file_groups')); $this->assertFalse(Cache::read('test_groups', 'file_groups')); $this->assertFalse(Cache::read('test_groups2', 'file_groups2')); $this->assertEquals('value 3', Cache::read('test_groups3', 'file_groups3')); $this->assertTrue(Cache::write('test_groups4', 'value', 'file_groups')); $this->assertTrue(Cache::write('test_groups5', 'value 2', 'file_groups2')); $this->assertTrue(Cache::write('test_groups6', 'value 3', 'file_groups3')); $this->assertTrue(Cache::clearGroup('group_b', 'file_groups')); $this->assertFalse(Cache::read('test_groups4', 'file_groups')); $this->assertFalse(Cache::read('test_groups5', 'file_groups2')); $this->assertEquals('value 3', Cache::read('test_groups6', 'file_groups3')); } /** * Test that clearGroup works with no prefix. * * @return void */ public function testGroupClearNoPrefix() { Cache::config('file_groups', array( 'engine' => 'File', 'duration' => 3600, 'prefix' => '', 'groups' => array('group_a', 'group_b') )); Cache::write('key_1', 'value', 'file_groups'); Cache::write('key_2', 'value', 'file_groups'); Cache::clearGroup('group_a', 'file_groups'); $this->assertFalse(Cache::read('key_1', 'file_groups'), 'Did not delete'); $this->assertFalse(Cache::read('key_2', 'file_groups'), 'Did not delete'); } /** * Test add method. * * @return void */ public function testAdd() { Cache::delete('test_add_key', 'file_test'); $result = Cache::add('test_add_key', 'test data', 'file_test'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'file_test'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'file_test'); $this->assertFalse($result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/RedisEngineTest.php0000644000000000000000000002607313365153155025074 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine * @since CakePHP(tm) v 2.2 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); App::uses('RedisEngine', 'Cache/Engine'); /** * RedisEngineTest class * * @package Cake.Test.Case.Cache.Engine */ class RedisEngineTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->skipIf(!class_exists('Redis'), 'Redis is not installed or configured properly.'); $this->_cacheDisable = Configure::read('Cache.disable'); Configure::write('Cache.disable', false); // @codingStandardsIgnoreStart $socket = @fsockopen('127.0.0.1', 6379, $errno, $errstr, 1); // @codingStandardsIgnoreEnd $this->skipIf(!$socket, 'Redis is not running.'); fclose($socket); Cache::config('redis', array( 'engine' => 'Redis', 'prefix' => 'cake_', 'duration' => 3600 )); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Configure::write('Cache.disable', $this->_cacheDisable); Cache::drop(''); Cache::drop('redis_groups'); Cache::drop('redis_helper'); Cache::config('default'); } /** * testSettings method * * @return void */ public function testSettings() { $settings = Cache::settings('redis'); $expecting = array( 'prefix' => 'cake_', 'duration' => 3600, 'probability' => 100, 'groups' => array(), 'engine' => 'Redis', 'server' => '127.0.0.1', 'port' => 6379, 'timeout' => 0, 'persistent' => true, 'password' => false, 'database' => 0, 'unix_socket' => false, ); $this->assertEquals($expecting, $settings); } /** * testConnect method * * @return void */ public function testConnect() { $Redis = new RedisEngine(); $this->assertTrue($Redis->init(Cache::settings('redis'))); } /** * testMultiDatabaseOperations method * * @return void */ public function testMultiDatabaseOperations() { Cache::config('redisdb0', array( 'engine' => 'Redis', 'prefix' => 'cake2_', 'duration' => 3600, 'persistent' => false, )); Cache::config('redisdb1', array( 'engine' => 'Redis', 'database' => 1, 'prefix' => 'cake2_', 'duration' => 3600, 'persistent' => false, )); $result = Cache::write('save_in_0', true, 'redisdb0'); $exist = Cache::read('save_in_0', 'redisdb0'); $this->assertTrue($result); $this->assertTrue($exist); $result = Cache::write('save_in_1', true, 'redisdb1'); $this->assertTrue($result); $exist = Cache::read('save_in_0', 'redisdb1'); $this->assertFalse($exist); $exist = Cache::read('save_in_1', 'redisdb1'); $this->assertTrue($exist); Cache::delete('save_in_0', 'redisdb0'); $exist = Cache::read('save_in_0', 'redisdb0'); $this->assertFalse($exist); Cache::delete('save_in_1', 'redisdb1'); $exist = Cache::read('save_in_1', 'redisdb1'); $this->assertFalse($exist); Cache::drop('redisdb0'); Cache::drop('redisdb1'); } /** * test write numbers method * * @return void */ public function testWriteNumbers() { $result = Cache::write('test-counter', 1, 'redis'); $this->assertSame(1, Cache::read('test-counter', 'redis')); $result = Cache::write('test-counter', 0, 'redis'); $this->assertSame(0, Cache::read('test-counter', 'redis')); $result = Cache::write('test-counter', -1, 'redis'); $this->assertSame(-1, Cache::read('test-counter', 'redis')); } /** * testReadAndWriteCache method * * @return void */ public function testReadAndWriteCache() { Cache::set(array('duration' => 1), null, 'redis'); $result = Cache::read('test', 'redis'); $expecting = ''; $this->assertEquals($expecting, $result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('test', $data, 'redis'); $this->assertTrue($result); $result = Cache::read('test', 'redis'); $expecting = $data; $this->assertEquals($expecting, $result); $data = array(1, 2, 3); $this->assertTrue(Cache::write('array_data', $data, 'redis')); $this->assertEquals($data, Cache::read('array_data', 'redis')); Cache::delete('test', 'redis'); } /** * testExpiry method * * @return void */ public function testExpiry() { Cache::set(array('duration' => 1), 'redis'); $result = Cache::read('test', 'redis'); $this->assertFalse($result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'redis'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'redis'); $this->assertFalse($result); Cache::set(array('duration' => "+1 second"), 'redis'); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'redis'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'redis'); $this->assertFalse($result); Cache::config('redis', array('duration' => '+1 second')); sleep(2); $result = Cache::read('other_test', 'redis'); $this->assertFalse($result); Cache::config('redis', array('duration' => '+29 days')); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('long_expiry_test', $data, 'redis'); $this->assertTrue($result); sleep(2); $result = Cache::read('long_expiry_test', 'redis'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::config('redis', array('duration' => 3600)); } /** * testDeleteCache method * * @return void */ public function testDeleteCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('delete_test', $data, 'redis'); $this->assertTrue($result); $result = Cache::delete('delete_test', 'redis'); $this->assertTrue($result); } /** * testDecrement method * * @return void */ public function testDecrement() { Cache::delete('test_decrement', 'redis'); $result = Cache::write('test_decrement', 5, 'redis'); $this->assertTrue($result); $result = Cache::decrement('test_decrement', 1, 'redis'); $this->assertEquals(4, $result); $result = Cache::read('test_decrement', 'redis'); $this->assertEquals(4, $result); $result = Cache::decrement('test_decrement', 2, 'redis'); $this->assertEquals(2, $result); $result = Cache::read('test_decrement', 'redis'); $this->assertEquals(2, $result); } /** * testIncrement method * * @return void */ public function testIncrement() { Cache::delete('test_increment', 'redis'); $result = Cache::increment('test_increment', 1, 'redis'); $this->assertEquals(1, $result); $result = Cache::read('test_increment', 'redis'); $this->assertEquals(1, $result); $result = Cache::increment('test_increment', 2, 'redis'); $this->assertEquals(3, $result); $result = Cache::read('test_increment', 'redis'); $this->assertEquals(3, $result); } /** * test clearing redis. * * @return void */ public function testClear() { Cache::config('redis2', array( 'engine' => 'Redis', 'prefix' => 'cake2_', 'duration' => 3600 )); Cache::write('some_value', 'cache1', 'redis'); $result = Cache::clear(true, 'redis'); $this->assertTrue($result); $this->assertEquals('cache1', Cache::read('some_value', 'redis')); Cache::write('some_value', 'cache2', 'redis2'); $result = Cache::clear(false, 'redis'); $this->assertTrue($result); $this->assertFalse(Cache::read('some_value', 'redis')); $this->assertEquals('cache2', Cache::read('some_value', 'redis2')); Cache::clear(false, 'redis2'); } /** * test that a 0 duration can successfully write. * * @return void */ public function testZeroDuration() { Cache::config('redis', array('duration' => 0)); $result = Cache::write('test_key', 'written!', 'redis'); $this->assertTrue($result); $result = Cache::read('test_key', 'redis'); $this->assertEquals('written!', $result); } /** * Tests that configuring groups for stored keys return the correct values when read/written * Shows that altering the group value is equivalent to deleting all keys under the same * group * * @return void */ public function testGroupReadWrite() { Cache::config('redis_groups', array( 'engine' => 'Redis', 'duration' => 3600, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); Cache::config('redis_helper', array( 'engine' => 'Redis', 'duration' => 3600, 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'redis_groups')); $this->assertEquals('value', Cache::read('test_groups', 'redis_groups')); Cache::increment('group_a', 1, 'redis_helper'); $this->assertFalse(Cache::read('test_groups', 'redis_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'redis_groups')); $this->assertEquals('value2', Cache::read('test_groups', 'redis_groups')); Cache::increment('group_b', 1, 'redis_helper'); $this->assertFalse(Cache::read('test_groups', 'redis_groups')); $this->assertTrue(Cache::write('test_groups', 'value3', 'redis_groups')); $this->assertEquals('value3', Cache::read('test_groups', 'redis_groups')); } /** * Tests that deleteing from a groups-enabled config is possible * * @return void */ public function testGroupDelete() { Cache::config('redis_groups', array( 'engine' => 'Redis', 'duration' => 3600, 'groups' => array('group_a', 'group_b') )); $this->assertTrue(Cache::write('test_groups', 'value', 'redis_groups')); $this->assertEquals('value', Cache::read('test_groups', 'redis_groups')); $this->assertTrue(Cache::delete('test_groups', 'redis_groups')); $this->assertFalse(Cache::read('test_groups', 'redis_groups')); } /** * Test clearing a cache group * * @return void */ public function testGroupClear() { Cache::config('redis_groups', array( 'engine' => 'Redis', 'duration' => 3600, 'groups' => array('group_a', 'group_b') )); $this->assertTrue(Cache::write('test_groups', 'value', 'redis_groups')); $this->assertTrue(Cache::clearGroup('group_a', 'redis_groups')); $this->assertFalse(Cache::read('test_groups', 'redis_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'redis_groups')); $this->assertTrue(Cache::clearGroup('group_b', 'redis_groups')); $this->assertFalse(Cache::read('test_groups', 'redis_groups')); } /** * Test add method. * * @return void */ public function testAdd() { Cache::delete('test_add_key', 'redis'); $result = Cache::add('test_add_key', 'test data', 'redis'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'redis'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'redis'); $this->assertFalse($result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/XcacheEngineTest.php0000644000000000000000000001705213365153155025216 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine * @since CakePHP(tm) v 1.2.0.5434 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); /** * XcacheEngineTest class * * @package Cake.Test.Case.Cache.Engine */ class XcacheEngineTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); if (!function_exists('xcache_set')) { $this->markTestSkipped('Xcache is not installed or configured properly'); } $this->_cacheDisable = Configure::read('Cache.disable'); Configure::write('Cache.disable', false); Cache::config('xcache', array('engine' => 'Xcache', 'prefix' => 'cake_')); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Configure::write('Cache.disable', $this->_cacheDisable); Cache::drop('xcache'); Cache::drop('xcache_groups'); Cache::config('default'); } /** * testSettings method * * @return void */ public function testSettings() { $settings = Cache::settings(); $expecting = array( 'prefix' => 'cake_', 'duration' => 3600, 'probability' => 100, 'engine' => 'Xcache', ); $this->assertTrue(isset($settings['PHP_AUTH_USER'])); $this->assertTrue(isset($settings['PHP_AUTH_PW'])); unset($settings['PHP_AUTH_USER'], $settings['PHP_AUTH_PW']); $this->assertEquals($settings, $expecting); } /** * testReadAndWriteCache method * * @return void */ public function testReadAndWriteCache() { Cache::set(array('duration' => 1)); $result = Cache::read('test'); $expecting = ''; $this->assertEquals($expecting, $result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('test', $data); $this->assertTrue($result); $result = Cache::read('test'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::delete('test'); } /** * testExpiry method * * @return void */ public function testExpiry() { Cache::set(array('duration' => 1)); $result = Cache::read('test'); $this->assertFalse($result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test'); $this->assertFalse($result); Cache::set(array('duration' => "+1 second")); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test'); $this->assertFalse($result); } /** * testDeleteCache method * * @return void */ public function testDeleteCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('delete_test', $data); $this->assertTrue($result); $result = Cache::delete('delete_test'); $this->assertTrue($result); } /** * testClearCache method * * @return void */ public function testClearCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('clear_test_1', $data); $this->assertTrue($result); $result = Cache::write('clear_test_2', $data); $this->assertTrue($result); $result = Cache::clear(); $this->assertTrue($result); } /** * testDecrement method * * @return void */ public function testDecrement() { $result = Cache::write('test_decrement', 5); $this->assertTrue($result); $result = Cache::decrement('test_decrement'); $this->assertEquals(4, $result); $result = Cache::read('test_decrement'); $this->assertEquals(4, $result); $result = Cache::decrement('test_decrement', 2); $this->assertEquals(2, $result); $result = Cache::read('test_decrement'); $this->assertEquals(2, $result); } /** * testIncrement method * * @return void */ public function testIncrement() { $result = Cache::write('test_increment', 5); $this->assertTrue($result); $result = Cache::increment('test_increment'); $this->assertEquals(6, $result); $result = Cache::read('test_increment'); $this->assertEquals(6, $result); $result = Cache::increment('test_increment', 2); $this->assertEquals(8, $result); $result = Cache::read('test_increment'); $this->assertEquals(8, $result); } /** * Tests that configuring groups for stored keys return the correct values when read/written * Shows that altering the group value is equivalent to deleting all keys under the same * group * * @return void */ public function testGroupsReadWrite() { Cache::config('xcache_groups', array( 'engine' => 'Xcache', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'xcache_groups')); $this->assertEquals('value', Cache::read('test_groups', 'xcache_groups')); xcache_inc('test_group_a', 1); $this->assertFalse(Cache::read('test_groups', 'xcache_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'xcache_groups')); $this->assertEquals('value2', Cache::read('test_groups', 'xcache_groups')); xcache_inc('test_group_b', 1); $this->assertFalse(Cache::read('test_groups', 'xcache_groups')); $this->assertTrue(Cache::write('test_groups', 'value3', 'xcache_groups')); $this->assertEquals('value3', Cache::read('test_groups', 'xcache_groups')); } /** * Tests that deleteing from a groups-enabled config is possible * * @return void */ public function testGroupDelete() { Cache::config('xcache_groups', array( 'engine' => 'Xcache', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'xcache_groups')); $this->assertEquals('value', Cache::read('test_groups', 'xcache_groups')); $this->assertTrue(Cache::delete('test_groups', 'xcache_groups')); $this->assertFalse(Cache::read('test_groups', 'xcache_groups')); } /** * Test clearing a cache group * * @return void */ public function testGroupClear() { Cache::config('xcache_groups', array( 'engine' => 'Xcache', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'xcache_groups')); $this->assertTrue(Cache::clearGroup('group_a', 'xcache_groups')); $this->assertFalse(Cache::read('test_groups', 'xcache_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'xcache_groups')); $this->assertTrue(Cache::clearGroup('group_b', 'xcache_groups')); $this->assertFalse(Cache::read('test_groups', 'xcache_groups')); } /** * Test that failed add write return false. * * @return void */ public function testAdd() { Cache::set(array('duration' => 1), null); Cache::delete('test_add_key', 'default'); $result = Cache::add('test_add_key', 'test data', 'default'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'default'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'default'); $this->assertFalse($result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/MemcacheEngineTest.php0000644000000000000000000003326213365153155025526 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine * @since CakePHP(tm) v 1.2.0.5434 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); App::uses('MemcacheEngine', 'Cache/Engine'); /** * TestMemcacheEngine * * @package Cake.Test.Case.Cache.Engine */ class TestMemcacheEngine extends MemcacheEngine { /** * public accessor to _parseServerString * * @param string $server * @return array */ public function parseServerString($server) { return $this->_parseServerString($server); } public function setMemcache($memcache) { $this->_Memcache = $memcache; } } /** * MemcacheEngineTest class * * @package Cake.Test.Case.Cache.Engine */ class MemcacheEngineTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->skipIf(!class_exists('Memcache'), 'Memcache is not installed or configured properly.'); $this->_cacheDisable = Configure::read('Cache.disable'); Configure::write('Cache.disable', false); Cache::config('memcache', array( 'engine' => 'Memcache', 'prefix' => 'cake_', 'duration' => 3600 )); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Configure::write('Cache.disable', $this->_cacheDisable); Cache::drop('memcache'); Cache::drop('memcache_groups'); Cache::drop('memcache_helper'); Cache::config('default'); } /** * testSettings method * * @return void */ public function testSettings() { $settings = Cache::settings('memcache'); unset($settings['serialize'], $settings['path']); $expecting = array( 'prefix' => 'cake_', 'duration' => 3600, 'probability' => 100, 'servers' => array('127.0.0.1'), 'persistent' => true, 'compress' => false, 'engine' => 'Memcache', 'groups' => array() ); $this->assertEquals($expecting, $settings); } /** * testSettings method * * @return void */ public function testMultipleServers() { $servers = array('127.0.0.1:11211', '127.0.0.1:11222'); $available = true; $Memcache = new Memcache(); foreach ($servers as $server) { list($host, $port) = explode(':', $server); //@codingStandardsIgnoreStart if (!@$Memcache->connect($host, $port)) { $available = false; } //@codingStandardsIgnoreEnd } $this->skipIf(!$available, 'Need memcache servers at ' . implode(', ', $servers) . ' to run this test.'); $Memcache = new MemcacheEngine(); $Memcache->init(array('engine' => 'Memcache', 'servers' => $servers)); $settings = $Memcache->settings(); $this->assertEquals($settings['servers'], $servers); Cache::drop('dual_server'); } /** * testConnect method * * @return void */ public function testConnect() { $Memcache = new MemcacheEngine(); $Memcache->init(Cache::settings('memcache')); $result = $Memcache->connect('127.0.0.1'); $this->assertTrue($result); } /** * test connecting to an ipv6 server. * * @return void */ public function testConnectIpv6() { $Memcache = new MemcacheEngine(); $result = $Memcache->init(array( 'prefix' => 'cake_', 'duration' => 200, 'engine' => 'Memcache', 'servers' => array( '[::1]:11211' ) )); $this->assertTrue($result); } /** * test domain starts with u * * @return void */ public function testParseServerStringWithU() { $Memcached = new TestMemcachedEngine(); $result = $Memcached->parseServerString('udomain.net:13211'); $this->assertEquals(array('udomain.net', '13211'), $result); } /** * test non latin domains. * * @return void */ public function testParseServerStringNonLatin() { $Memcache = new TestMemcacheEngine(); $result = $Memcache->parseServerString('schรผlervz.net:13211'); $this->assertEquals(array('schรผlervz.net', '13211'), $result); $result = $Memcache->parseServerString('sรผlรผl:1111'); $this->assertEquals(array('sรผlรผl', '1111'), $result); } /** * test unix sockets. * * @return void */ public function testParseServerStringUnix() { $Memcache = new TestMemcacheEngine(); $result = $Memcache->parseServerString('unix:///path/to/memcached.sock'); $this->assertEquals(array('unix:///path/to/memcached.sock', 0), $result); } /** * testReadAndWriteCache method * * @return void */ public function testReadAndWriteCache() { Cache::set(array('duration' => 1), null, 'memcache'); $result = Cache::read('test', 'memcache'); $expecting = ''; $this->assertEquals($expecting, $result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('test', $data, 'memcache'); $this->assertTrue($result); $result = Cache::read('test', 'memcache'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::delete('test', 'memcache'); } /** * testExpiry method * * @return void */ public function testExpiry() { Cache::set(array('duration' => 1), 'memcache'); $result = Cache::read('test', 'memcache'); $this->assertFalse($result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'memcache'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'memcache'); $this->assertFalse($result); Cache::set(array('duration' => "+1 second"), 'memcache'); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'memcache'); $this->assertTrue($result); sleep(3); $result = Cache::read('other_test', 'memcache'); $this->assertFalse($result); Cache::config('memcache', array('duration' => '+1 second')); $result = Cache::read('other_test', 'memcache'); $this->assertFalse($result); Cache::config('memcache', array('duration' => '+29 days')); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('long_expiry_test', $data, 'memcache'); $this->assertTrue($result); sleep(2); $result = Cache::read('long_expiry_test', 'memcache'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::config('memcache', array('duration' => 3600)); } /** * testDeleteCache method * * @return void */ public function testDeleteCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('delete_test', $data, 'memcache'); $this->assertTrue($result); $result = Cache::delete('delete_test', 'memcache'); $this->assertTrue($result); } /** * testDecrement method * * @return void */ public function testDecrement() { $result = Cache::write('test_decrement', 5, 'memcache'); $this->assertTrue($result); $result = Cache::decrement('test_decrement', 1, 'memcache'); $this->assertEquals(4, $result); $result = Cache::read('test_decrement', 'memcache'); $this->assertEquals(4, $result); $result = Cache::decrement('test_decrement', 2, 'memcache'); $this->assertEquals(2, $result); $result = Cache::read('test_decrement', 'memcache'); $this->assertEquals(2, $result); } /** * testIncrement method * * @return void */ public function testIncrement() { $result = Cache::write('test_increment', 5, 'memcache'); $this->assertTrue($result); $result = Cache::increment('test_increment', 1, 'memcache'); $this->assertEquals(6, $result); $result = Cache::read('test_increment', 'memcache'); $this->assertEquals(6, $result); $result = Cache::increment('test_increment', 2, 'memcache'); $this->assertEquals(8, $result); $result = Cache::read('test_increment', 'memcache'); $this->assertEquals(8, $result); } /** * test that configurations don't conflict, when a file engine is declared after a memcache one. * * @return void */ public function testConfigurationConflict() { Cache::config('long_memcache', array( 'engine' => 'Memcache', 'duration' => '+2 seconds', 'servers' => array('127.0.0.1:11211'), )); Cache::config('short_memcache', array( 'engine' => 'Memcache', 'duration' => '+1 seconds', 'servers' => array('127.0.0.1:11211'), )); Cache::config('some_file', array('engine' => 'File')); $this->assertTrue(Cache::write('duration_test', 'yay', 'long_memcache')); $this->assertTrue(Cache::write('short_duration_test', 'boo', 'short_memcache')); $this->assertEquals('yay', Cache::read('duration_test', 'long_memcache'), 'Value was not read %s'); $this->assertEquals('boo', Cache::read('short_duration_test', 'short_memcache'), 'Value was not read %s'); sleep(1); $this->assertEquals('yay', Cache::read('duration_test', 'long_memcache'), 'Value was not read %s'); sleep(2); $this->assertFalse(Cache::read('short_duration_test', 'short_memcache'), 'Cache was not invalidated %s'); $this->assertFalse(Cache::read('duration_test', 'long_memcache'), 'Value did not expire %s'); Cache::delete('duration_test', 'long_memcache'); Cache::delete('short_duration_test', 'short_memcache'); } /** * test clearing memcache. * * @return void */ public function testClear() { Cache::config('memcache2', array( 'engine' => 'Memcache', 'prefix' => 'cake2_', 'duration' => 3600 )); Cache::write('some_value', 'cache1', 'memcache'); $result = Cache::clear(true, 'memcache'); $this->assertTrue($result); $this->assertEquals('cache1', Cache::read('some_value', 'memcache')); Cache::write('some_value', 'cache2', 'memcache2'); $result = Cache::clear(false, 'memcache'); $this->assertTrue($result); $this->assertFalse(Cache::read('some_value', 'memcache')); $this->assertEquals('cache2', Cache::read('some_value', 'memcache2')); Cache::clear(false, 'memcache2'); } /** * test that a 0 duration can successfully write. * * @return void */ public function testZeroDuration() { Cache::config('memcache', array('duration' => 0)); $result = Cache::write('test_key', 'written!', 'memcache'); $this->assertTrue($result); $result = Cache::read('test_key', 'memcache'); $this->assertEquals('written!', $result); } /** * test that durations greater than 30 days never expire * * @return void */ public function testLongDurationEqualToZero() { $memcache = new TestMemcacheEngine(); $memcache->settings['compress'] = false; $mock = $this->getMock('Memcache'); $memcache->setMemcache($mock); $mock->expects($this->once()) ->method('set') ->with('key', 'value', false, 0); $value = 'value'; $memcache->write('key', $value, 50 * DAY); } /** * Tests that configuring groups for stored keys return the correct values when read/written * Shows that altering the group value is equivalent to deleting all keys under the same * group * * @return void */ public function testGroupReadWrite() { Cache::config('memcache_groups', array( 'engine' => 'Memcache', 'duration' => 3600, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); Cache::config('memcache_helper', array( 'engine' => 'Memcache', 'duration' => 3600, 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'memcache_groups')); $this->assertEquals('value', Cache::read('test_groups', 'memcache_groups')); Cache::increment('group_a', 1, 'memcache_helper'); $this->assertFalse(Cache::read('test_groups', 'memcache_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'memcache_groups')); $this->assertEquals('value2', Cache::read('test_groups', 'memcache_groups')); Cache::increment('group_b', 1, 'memcache_helper'); $this->assertFalse(Cache::read('test_groups', 'memcache_groups')); $this->assertTrue(Cache::write('test_groups', 'value3', 'memcache_groups')); $this->assertEquals('value3', Cache::read('test_groups', 'memcache_groups')); } /** * Tests that deleteing from a groups-enabled config is possible * * @return void */ public function testGroupDelete() { Cache::config('memcache_groups', array( 'engine' => 'Memcache', 'duration' => 3600, 'groups' => array('group_a', 'group_b') )); $this->assertTrue(Cache::write('test_groups', 'value', 'memcache_groups')); $this->assertEquals('value', Cache::read('test_groups', 'memcache_groups')); $this->assertTrue(Cache::delete('test_groups', 'memcache_groups')); $this->assertFalse(Cache::read('test_groups', 'memcache_groups')); } /** * Test clearing a cache group * * @return void */ public function testGroupClear() { Cache::config('memcache_groups', array( 'engine' => 'Memcache', 'duration' => 3600, 'groups' => array('group_a', 'group_b') )); $this->assertTrue(Cache::write('test_groups', 'value', 'memcache_groups')); $this->assertTrue(Cache::clearGroup('group_a', 'memcache_groups')); $this->assertFalse(Cache::read('test_groups', 'memcache_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'memcache_groups')); $this->assertTrue(Cache::clearGroup('group_b', 'memcache_groups')); $this->assertFalse(Cache::read('test_groups', 'memcache_groups')); } /** * Test that failed add write return false. * * @return void */ public function testAdd() { Cache::delete('test_add_key', 'memcache'); $result = Cache::add('test_add_key', 'test data', 'memcache'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'memcache'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'memcache'); $this->assertFalse($result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php0000644000000000000000000005312713365153155025674 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine * @since CakePHP(tm) v 2.5.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); App::uses('MemcachedEngine', 'Cache/Engine'); /** * TestMemcachedEngine * * @package Cake.Test.Case.Cache.Engine */ class TestMemcachedEngine extends MemcachedEngine { /** * public accessor to _parseServerString * * @param string $server * @return array */ public function parseServerString($server) { return $this->_parseServerString($server); } public function setMemcached($memcached) { $this->_Memcached = $memcached; } public function getMemcached() { return $this->_Memcached; } } /** * MemcachedEngineTest class * * @package Cake.Test.Case.Cache.Engine */ class MemcachedEngineTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->skipIf(!class_exists('Memcached'), 'Memcached is not installed or configured properly.'); // @codingStandardsIgnoreStart $socket = @fsockopen('127.0.0.1', 11211, $errno, $errstr, 1); // @codingStandardsIgnoreEnd $this->skipIf(!$socket, 'Memcached is not running.'); fclose($socket); Cache::config('memcached', array( 'engine' => 'Memcached', 'prefix' => 'cake_', 'duration' => 3600 )); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Cache::drop('memcached'); Cache::drop('memcached_groups'); Cache::drop('memcached_helper'); Cache::config('default'); } /** * testSettings method * * @return void */ public function testSettings() { $settings = Cache::settings('memcached'); unset($settings['path']); $expecting = array( 'prefix' => 'cake_', 'duration' => 3600, 'probability' => 100, 'servers' => array('127.0.0.1'), 'persistent' => false, 'compress' => false, 'engine' => 'Memcached', 'login' => null, 'password' => null, 'groups' => array(), 'serialize' => 'php', 'options' => array() ); $this->assertEquals($expecting, $settings); } /** * testCompressionSetting method * * @return void */ public function testCompressionSetting() { $Memcached = new TestMemcachedEngine(); $Memcached->init(array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'compress' => false )); $this->assertFalse($Memcached->getMemcached()->getOption(Memcached::OPT_COMPRESSION)); $MemcachedCompressed = new TestMemcachedEngine(); $MemcachedCompressed->init(array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'compress' => true )); $this->assertTrue($MemcachedCompressed->getMemcached()->getOption(Memcached::OPT_COMPRESSION)); } /** * test setting options * * @return void */ public function testOptionsSetting() { $memcached = new TestMemcachedEngine(); $memcached->init(array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'options' => array( Memcached::OPT_BINARY_PROTOCOL => true ) )); $this->assertEquals(1, $memcached->getMemcached()->getOption(Memcached::OPT_BINARY_PROTOCOL)); } /** * test accepts only valid serializer engine * * @return void */ public function testInvalidSerializerSetting() { $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'invalid_serializer' ); $this->setExpectedException( 'CacheException', 'invalid_serializer is not a valid serializer engine for Memcached' ); $Memcached->init($settings); } /** * testPhpSerializerSetting method * * @return void */ public function testPhpSerializerSetting() { $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'php' ); $Memcached->init($settings); $this->assertEquals(Memcached::SERIALIZER_PHP, $Memcached->getMemcached()->getOption(Memcached::OPT_SERIALIZER)); } /** * testJsonSerializerSetting method * * @return void */ public function testJsonSerializerSetting() { $this->skipIf( !Memcached::HAVE_JSON, 'Memcached extension is not compiled with json support' ); $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'json' ); $Memcached->init($settings); $this->assertEquals(Memcached::SERIALIZER_JSON, $Memcached->getMemcached()->getOption(Memcached::OPT_SERIALIZER)); } /** * testIgbinarySerializerSetting method * * @return void */ public function testIgbinarySerializerSetting() { $this->skipIf( !Memcached::HAVE_IGBINARY, 'Memcached extension is not compiled with igbinary support' ); $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'igbinary' ); $Memcached->init($settings); $this->assertEquals(Memcached::SERIALIZER_IGBINARY, $Memcached->getMemcached()->getOption(Memcached::OPT_SERIALIZER)); } /** * testMsgpackSerializerSetting method * * @return void */ public function testMsgpackSerializerSetting() { $this->skipIf( !defined('Memcached::HAVE_MSGPACK') || !Memcached::HAVE_MSGPACK, 'Memcached extension is not compiled with msgpack support' ); $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'msgpack' ); $Memcached->init($settings); $this->assertEquals(Memcached::SERIALIZER_MSGPACK, $Memcached->getMemcached()->getOption(Memcached::OPT_SERIALIZER)); } /** * testJsonSerializerThrowException method * * @return void */ public function testJsonSerializerThrowException() { $this->skipIf( Memcached::HAVE_JSON, 'Memcached extension is compiled with json support' ); $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'json' ); $this->setExpectedException( 'CacheException', 'Memcached extension is not compiled with json support' ); $Memcached->init($settings); } /** * testMsgpackSerializerThrowException method * * @return void */ public function testMsgpackSerializerThrowException() { $this->skipIf( defined('Memcached::HAVE_MSGPACK') && Memcached::HAVE_MSGPACK, 'Memcached extension is compiled with msgpack support' ); $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'msgpack' ); $this->setExpectedException( 'CacheException', 'msgpack is not a valid serializer engine for Memcached' ); $Memcached->init($settings); } /** * testIgbinarySerializerThrowException method * * @return void */ public function testIgbinarySerializerThrowException() { $this->skipIf( Memcached::HAVE_IGBINARY, 'Memcached extension is compiled with igbinary support' ); $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'serialize' => 'igbinary' ); $this->setExpectedException( 'CacheException', 'Memcached extension is not compiled with igbinary support' ); $Memcached->init($settings); } /** * test using authentication without memcached installed with SASL support * throw an exception * * @return void */ public function testSaslAuthException() { $this->skipIf(version_compare(PHP_VERSION, '7.0.0', '>=')); $Memcached = new TestMemcachedEngine(); $settings = array( 'engine' => 'Memcached', 'servers' => array('127.0.0.1:11211'), 'persistent' => false, 'login' => 'test', 'password' => 'password' ); $this->setExpectedException('PHPUnit_Framework_Error_Warning'); $Memcached->init($settings); } /** * testSettings method * * @return void */ public function testMultipleServers() { $servers = array('127.0.0.1:11211', '127.0.0.1:11222'); $available = true; $Memcached = new Memcached(); foreach ($servers as $server) { list($host, $port) = explode(':', $server); //@codingStandardsIgnoreStart if (!$Memcached->addServer($host, $port)) { $available = false; } //@codingStandardsIgnoreEnd } $this->skipIf(!$available, 'Need memcached servers at ' . implode(', ', $servers) . ' to run this test.'); $Memcached = new MemcachedEngine(); $Memcached->init(array('engine' => 'Memcached', 'servers' => $servers)); $settings = $Memcached->settings(); $this->assertEquals($settings['servers'], $servers); Cache::drop('dual_server'); } /** * test connecting to an ipv6 server. * * @return void */ public function testConnectIpv6() { $Memcached = new MemcachedEngine(); $result = $Memcached->init(array( 'prefix' => 'cake_', 'duration' => 200, 'engine' => 'Memcached', 'servers' => array( '[::1]:11211' ) )); $this->assertTrue($result); } /** * test domain starts with u * * @return void */ public function testParseServerStringWithU() { $Memcached = new TestMemcachedEngine(); $result = $Memcached->parseServerString('udomain.net:13211'); $this->assertEquals(array('udomain.net', '13211'), $result); } /** * test non latin domains. * * @return void */ public function testParseServerStringNonLatin() { $Memcached = new TestMemcachedEngine(); $result = $Memcached->parseServerString('schรผlervz.net:13211'); $this->assertEquals(array('schรผlervz.net', '13211'), $result); $result = $Memcached->parseServerString('sรผlรผl:1111'); $this->assertEquals(array('sรผlรผl', '1111'), $result); } /** * test unix sockets. * * @return void */ public function testParseServerStringUnix() { $Memcached = new TestMemcachedEngine(); $result = $Memcached->parseServerString('unix:///path/to/memcachedd.sock'); $this->assertEquals(array('/path/to/memcachedd.sock', 0), $result); } /** * testReadAndWriteCache method * * @return void */ public function testReadAndWriteCache() { Cache::set(array('duration' => 1), null, 'memcached'); $result = Cache::read('test', 'memcached'); $expecting = ''; $this->assertEquals($expecting, $result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('test', $data, 'memcached'); $this->assertTrue($result); $result = Cache::read('test', 'memcached'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::delete('test', 'memcached'); } /** * testExpiry method * * @return void */ public function testExpiry() { Cache::set(array('duration' => 1), 'memcached'); $result = Cache::read('test', 'memcached'); $this->assertFalse($result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'memcached'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'memcached'); $this->assertFalse($result); Cache::set(array('duration' => "+1 second"), 'memcached'); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'memcached'); $this->assertTrue($result); sleep(3); $result = Cache::read('other_test', 'memcached'); $this->assertFalse($result); Cache::config('memcached', array('duration' => '+1 second')); $result = Cache::read('other_test', 'memcached'); $this->assertFalse($result); Cache::config('memcached', array('duration' => '+29 days')); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('long_expiry_test', $data, 'memcached'); $this->assertTrue($result); sleep(2); $result = Cache::read('long_expiry_test', 'memcached'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::config('memcached', array('duration' => 3600)); } /** * testDeleteCache method * * @return void */ public function testDeleteCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('delete_test', $data, 'memcached'); $this->assertTrue($result); $result = Cache::delete('delete_test', 'memcached'); $this->assertTrue($result); } /** * testDecrement method * * @return void */ public function testDecrement() { $result = Cache::write('test_decrement', 5, 'memcached'); $this->assertTrue($result); $result = Cache::decrement('test_decrement', 1, 'memcached'); $this->assertEquals(4, $result); $result = Cache::read('test_decrement', 'memcached'); $this->assertEquals(4, $result); $result = Cache::decrement('test_decrement', 2, 'memcached'); $this->assertEquals(2, $result); $result = Cache::read('test_decrement', 'memcached'); $this->assertEquals(2, $result); Cache::delete('test_decrement', 'memcached'); } /** * test decrementing compressed keys * * @return void */ public function testDecrementCompressedKeys() { Cache::config('compressed_memcached', array( 'engine' => 'Memcached', 'duration' => '+2 seconds', 'servers' => array('127.0.0.1:11211'), 'compress' => true )); $result = Cache::write('test_decrement', 5, 'compressed_memcached'); $this->assertTrue($result); $result = Cache::decrement('test_decrement', 1, 'compressed_memcached'); $this->assertEquals(4, $result); $result = Cache::read('test_decrement', 'compressed_memcached'); $this->assertEquals(4, $result); $result = Cache::decrement('test_decrement', 2, 'compressed_memcached'); $this->assertEquals(2, $result); $result = Cache::read('test_decrement', 'compressed_memcached'); $this->assertEquals(2, $result); Cache::delete('test_decrement', 'compressed_memcached'); } /** * testIncrement method * * @return void */ public function testIncrement() { $result = Cache::write('test_increment', 5, 'memcached'); $this->assertTrue($result); $result = Cache::increment('test_increment', 1, 'memcached'); $this->assertEquals(6, $result); $result = Cache::read('test_increment', 'memcached'); $this->assertEquals(6, $result); $result = Cache::increment('test_increment', 2, 'memcached'); $this->assertEquals(8, $result); $result = Cache::read('test_increment', 'memcached'); $this->assertEquals(8, $result); Cache::delete('test_increment', 'memcached'); } /** * test incrementing compressed keys * * @return void */ public function testIncrementCompressedKeys() { Cache::config('compressed_memcached', array( 'engine' => 'Memcached', 'duration' => '+2 seconds', 'servers' => array('127.0.0.1:11211'), 'compress' => true )); $result = Cache::write('test_increment', 5, 'compressed_memcached'); $this->assertTrue($result); $result = Cache::increment('test_increment', 1, 'compressed_memcached'); $this->assertEquals(6, $result); $result = Cache::read('test_increment', 'compressed_memcached'); $this->assertEquals(6, $result); $result = Cache::increment('test_increment', 2, 'compressed_memcached'); $this->assertEquals(8, $result); $result = Cache::read('test_increment', 'compressed_memcached'); $this->assertEquals(8, $result); Cache::delete('test_increment', 'compressed_memcached'); } /** * test that configurations don't conflict, when a file engine is declared after a memcached one. * * @return void */ public function testConfigurationConflict() { Cache::config('long_memcached', array( 'engine' => 'Memcached', 'duration' => '+2 seconds', 'servers' => array('127.0.0.1:11211'), )); Cache::config('short_memcached', array( 'engine' => 'Memcached', 'duration' => '+1 seconds', 'servers' => array('127.0.0.1:11211'), )); Cache::config('some_file', array('engine' => 'File')); $this->assertTrue(Cache::write('duration_test', 'yay', 'long_memcached')); $this->assertTrue(Cache::write('short_duration_test', 'boo', 'short_memcached')); $this->assertEquals('yay', Cache::read('duration_test', 'long_memcached'), 'Value was not read %s'); $this->assertEquals('boo', Cache::read('short_duration_test', 'short_memcached'), 'Value was not read %s'); sleep(1); $this->assertEquals('yay', Cache::read('duration_test', 'long_memcached'), 'Value was not read %s'); sleep(2); $this->assertFalse(Cache::read('short_duration_test', 'short_memcached'), 'Cache was not invalidated %s'); $this->assertFalse(Cache::read('duration_test', 'long_memcached'), 'Value did not expire %s'); Cache::delete('duration_test', 'long_memcached'); Cache::delete('short_duration_test', 'short_memcached'); } /** * test clearing memcached. * * @return void */ public function testClear() { Cache::config('memcached2', array( 'engine' => 'Memcached', 'prefix' => 'cake2_', 'duration' => 3600 )); Cache::write('some_value', 'cache1', 'memcached'); $result = Cache::clear(true, 'memcached'); $this->assertTrue($result); $this->assertEquals('cache1', Cache::read('some_value', 'memcached')); Cache::write('some_value', 'cache2', 'memcached2'); $result = Cache::clear(false, 'memcached'); $this->assertTrue($result); $this->assertFalse(Cache::read('some_value', 'memcached')); $this->assertEquals('cache2', Cache::read('some_value', 'memcached2')); Cache::clear(false, 'memcached2'); } /** * test that a 0 duration can successfully write. * * @return void */ public function testZeroDuration() { Cache::config('memcached', array('duration' => 0)); $result = Cache::write('test_key', 'written!', 'memcached'); $this->assertTrue($result); $result = Cache::read('test_key', 'memcached'); $this->assertEquals('written!', $result); } /** * test that durations greater than 30 days never expire * * @return void */ public function testLongDurationEqualToZero() { $this->markTestSkipped('Cannot run as Memcached cannot be reflected'); $memcached = new TestMemcachedEngine(); $memcached->settings['compress'] = false; $mock = $this->getMock('Memcached'); $memcached->setMemcached($mock); $mock->expects($this->once()) ->method('set') ->with('key', 'value', 0); $value = 'value'; $memcached->write('key', $value, 50 * DAY); } /** * Tests that configuring groups for stored keys return the correct values when read/written * Shows that altering the group value is equivalent to deleting all keys under the same * group * * @return void */ public function testGroupReadWrite() { Cache::config('memcached_groups', array( 'engine' => 'Memcached', 'duration' => 3600, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); Cache::config('memcached_helper', array( 'engine' => 'Memcached', 'duration' => 3600, 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups')); $this->assertEquals('value', Cache::read('test_groups', 'memcached_groups')); Cache::increment('group_a', 1, 'memcached_helper'); $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'memcached_groups')); $this->assertEquals('value2', Cache::read('test_groups', 'memcached_groups')); Cache::increment('group_b', 1, 'memcached_helper'); $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); $this->assertTrue(Cache::write('test_groups', 'value3', 'memcached_groups')); $this->assertEquals('value3', Cache::read('test_groups', 'memcached_groups')); } /** * Tests that deleteing from a groups-enabled config is possible * * @return void */ public function testGroupDelete() { Cache::config('memcached_groups', array( 'engine' => 'Memcached', 'duration' => 3600, 'groups' => array('group_a', 'group_b') )); $this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups')); $this->assertEquals('value', Cache::read('test_groups', 'memcached_groups')); $this->assertTrue(Cache::delete('test_groups', 'memcached_groups')); $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); } /** * Test clearing a cache group * * @return void */ public function testGroupClear() { Cache::config('memcached_groups', array( 'engine' => 'Memcached', 'duration' => 3600, 'groups' => array('group_a', 'group_b') )); $this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups')); $this->assertTrue(Cache::clearGroup('group_a', 'memcached_groups')); $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'memcached_groups')); $this->assertTrue(Cache::clearGroup('group_b', 'memcached_groups')); $this->assertFalse(Cache::read('test_groups', 'memcached_groups')); } /** * Test add method. * * @return void */ public function testAdd() { Cache::set(array('duration' => 1), null, 'memcached'); Cache::delete('test_add_key', 'default'); $result = Cache::add('test_add_key', 'test data', 'default'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'default'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'default'); $this->assertFalse($result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/ApcEngineTest.php0000644000000000000000000002056113365153155024525 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine * @since CakePHP(tm) v 1.2.0.5434 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); /** * ApcEngineTest class * * @package Cake.Test.Case.Cache.Engine */ class ApcEngineTest extends CakeTestCase { /** * APC extension to be used * * @var string */ protected $_apcExtension = 'apc'; /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $hasApc = extension_loaded('apc') || extension_loaded('apcu'); $this->skipIf(!$hasApc, 'Apc is not installed or configured properly.'); if (PHP_SAPI === 'cli') { $this->skipIf(!ini_get('apc.enable_cli'), 'APC is not enabled for the CLI.'); } if (extension_loaded('apcu')) { $this->_apcExtension = 'apcu'; } $this->_cacheDisable = Configure::read('Cache.disable'); Configure::write('Cache.disable', false); Cache::config('apc', array('engine' => 'Apc', 'prefix' => 'cake_')); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Configure::write('Cache.disable', $this->_cacheDisable); Cache::drop('apc'); Cache::drop('apc_groups'); Cache::config('default'); } /** * testReadAndWriteCache method * * @return void */ public function testReadAndWriteCache() { Cache::set(array('duration' => 1), 'apc'); $result = Cache::read('test', 'apc'); $expecting = ''; $this->assertEquals($expecting, $result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('test', $data, 'apc'); $this->assertTrue($result); $result = Cache::read('test', 'apc'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::delete('test', 'apc'); } /** * Writing cache entries with duration = 0 (forever) should work. * * @return void */ public function testReadWriteDurationZero() { Cache::config('apc', array('engine' => 'Apc', 'duration' => 0, 'prefix' => 'cake_')); Cache::write('zero', 'Should save', 'apc'); sleep(1); $result = Cache::read('zero', 'apc'); $this->assertEquals('Should save', $result); } /** * testExpiry method * * @return void */ public function testExpiry() { Cache::set(array('duration' => 1), 'apc'); $result = Cache::read('test', 'apc'); $this->assertFalse($result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'apc'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'apc'); $this->assertFalse($result); Cache::set(array('duration' => 1), 'apc'); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'apc'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'apc'); $this->assertFalse($result); sleep(2); $result = Cache::read('other_test', 'apc'); $this->assertFalse($result); } /** * testDeleteCache method * * @return void */ public function testDeleteCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('delete_test', $data, 'apc'); $this->assertTrue($result); $result = Cache::delete('delete_test', 'apc'); $this->assertTrue($result); } /** * testDecrement method * * @return void */ public function testDecrement() { $hasSupport = function_exists('apc_dec') || function_exists('apcu_dec'); $this->skipIf(!$hasSupport, 'No apc_dec()/apcu_dec() function, cannot test decrement().'); $result = Cache::write('test_decrement', 5, 'apc'); $this->assertTrue($result); $result = Cache::decrement('test_decrement', 1, 'apc'); $this->assertEquals(4, $result); $result = Cache::read('test_decrement', 'apc'); $this->assertEquals(4, $result); $result = Cache::decrement('test_decrement', 2, 'apc'); $this->assertEquals(2, $result); $result = Cache::read('test_decrement', 'apc'); $this->assertEquals(2, $result); } /** * testIncrement method * * @return void */ public function testIncrement() { $hasSupport = function_exists('apc_inc') || function_exists('apcu_inc'); $this->skipIf(!function_exists('apc_inc'), 'No apc_inc()/apcu_inc() function, cannot test increment().'); $result = Cache::write('test_increment', 5, 'apc'); $this->assertTrue($result); $result = Cache::increment('test_increment', 1, 'apc'); $this->assertEquals(6, $result); $result = Cache::read('test_increment', 'apc'); $this->assertEquals(6, $result); $result = Cache::increment('test_increment', 2, 'apc'); $this->assertEquals(8, $result); $result = Cache::read('test_increment', 'apc'); $this->assertEquals(8, $result); } /** * test the clearing of cache keys * * @return void */ public function testClear() { $storeFunc = $this->_apcExtension . '_store'; $fetchFunc = $this->_apcExtension . '_fetch'; $deleteFunc = $this->_apcExtension . '_delete'; $storeFunc('not_cake', 'survive'); Cache::write('some_value', 'value', 'apc'); $result = Cache::clear(false, 'apc'); $this->assertTrue($result); $this->assertFalse(Cache::read('some_value', 'apc')); $this->assertEquals('survive', $fetchFunc('not_cake')); $deleteFunc('not_cake'); } /** * Tests that configuring groups for stored keys return the correct values when read/written * Shows that altering the group value is equivalent to deleting all keys under the same * group * * @return void */ public function testGroupsReadWrite() { $incFunc = $this->_apcExtension . '_inc'; Cache::config('apc_groups', array( 'engine' => 'Apc', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'apc_groups')); $this->assertEquals('value', Cache::read('test_groups', 'apc_groups')); $incFunc('test_group_a'); $this->assertFalse(Cache::read('test_groups', 'apc_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'apc_groups')); $this->assertEquals('value2', Cache::read('test_groups', 'apc_groups')); $incFunc('test_group_b'); $this->assertFalse(Cache::read('test_groups', 'apc_groups')); $this->assertTrue(Cache::write('test_groups', 'value3', 'apc_groups')); $this->assertEquals('value3', Cache::read('test_groups', 'apc_groups')); } /** * Tests that deleteing from a groups-enabled config is possible * * @return void */ public function testGroupDelete() { Cache::config('apc_groups', array( 'engine' => 'Apc', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'apc_groups')); $this->assertEquals('value', Cache::read('test_groups', 'apc_groups')); $this->assertTrue(Cache::delete('test_groups', 'apc_groups')); $this->assertFalse(Cache::read('test_groups', 'apc_groups')); } /** * Test clearing a cache group * * @return void */ public function testGroupClear() { Cache::config('apc_groups', array( 'engine' => 'Apc', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'apc_groups')); $this->assertTrue(Cache::clearGroup('group_a', 'apc_groups')); $this->assertFalse(Cache::read('test_groups', 'apc_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'apc_groups')); $this->assertTrue(Cache::clearGroup('group_b', 'apc_groups')); $this->assertFalse(Cache::read('test_groups', 'apc_groups')); } /** * Test add method. * * @return void */ public function testAdd() { Cache::delete('test_add_key', 'apc'); $result = Cache::add('test_add_key', 'test data', 'apc'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'apc'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'apc'); $this->assertFalse($result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/Engine/WincacheEngineTest.php0000644000000000000000000001737713365153155025556 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache.Engine * @since CakePHP(tm) v 1.2.0.5434 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); /** * WincacheEngineTest class * * @package Cake.Test.Case.Cache.Engine */ class WincacheEngineTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->skipIf(!function_exists('wincache_ucache_set'), 'Wincache is not installed or configured properly.'); $this->_cacheDisable = Configure::read('Cache.disable'); Configure::write('Cache.disable', false); Cache::config('wincache', array('engine' => 'Wincache', 'prefix' => 'cake_')); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Configure::write('Cache.disable', $this->_cacheDisable); Cache::drop('wincache'); Cache::drop('wincache_groups'); Cache::config('default'); } /** * testReadAndWriteCache method * * @return void */ public function testReadAndWriteCache() { Cache::set(array('duration' => 1), 'wincache'); $result = Cache::read('test', 'wincache'); $expecting = ''; $this->assertEquals($expecting, $result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('test', $data, 'wincache'); $this->assertTrue($result); $result = Cache::read('test', 'wincache'); $expecting = $data; $this->assertEquals($expecting, $result); Cache::delete('test', 'wincache'); } /** * testExpiry method * * @return void */ public function testExpiry() { Cache::set(array('duration' => 1), 'wincache'); $result = Cache::read('test', 'wincache'); $this->assertFalse($result); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'wincache'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'wincache'); $this->assertFalse($result); Cache::set(array('duration' => 1), 'wincache'); $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('other_test', $data, 'wincache'); $this->assertTrue($result); sleep(2); $result = Cache::read('other_test', 'wincache'); $this->assertFalse($result); sleep(2); $result = Cache::read('other_test', 'wincache'); $this->assertFalse($result); } /** * testDeleteCache method * * @return void */ public function testDeleteCache() { $data = 'this is a test of the emergency broadcasting system'; $result = Cache::write('delete_test', $data, 'wincache'); $this->assertTrue($result); $result = Cache::delete('delete_test', 'wincache'); $this->assertTrue($result); } /** * testDecrement method * * @return void */ public function testDecrement() { $this->skipIf( !function_exists('wincache_ucache_dec'), 'No wincache_ucache_dec() function, cannot test decrement().' ); $result = Cache::write('test_decrement', 5, 'wincache'); $this->assertTrue($result); $result = Cache::decrement('test_decrement', 1, 'wincache'); $this->assertEquals(4, $result); $result = Cache::read('test_decrement', 'wincache'); $this->assertEquals(4, $result); $result = Cache::decrement('test_decrement', 2, 'wincache'); $this->assertEquals(2, $result); $result = Cache::read('test_decrement', 'wincache'); $this->assertEquals(2, $result); } /** * testIncrement method * * @return void */ public function testIncrement() { $this->skipIf( !function_exists('wincache_ucache_inc'), 'No wincache_inc() function, cannot test increment().' ); $result = Cache::write('test_increment', 5, 'wincache'); $this->assertTrue($result); $result = Cache::increment('test_increment', 1, 'wincache'); $this->assertEquals(6, $result); $result = Cache::read('test_increment', 'wincache'); $this->assertEquals(6, $result); $result = Cache::increment('test_increment', 2, 'wincache'); $this->assertEquals(8, $result); $result = Cache::read('test_increment', 'wincache'); $this->assertEquals(8, $result); } /** * test the clearing of cache keys * * @return void */ public function testClear() { wincache_ucache_set('not_cake', 'safe'); Cache::write('some_value', 'value', 'wincache'); $result = Cache::clear(false, 'wincache'); $this->assertTrue($result); $this->assertFalse(Cache::read('some_value', 'wincache')); $this->assertEquals('safe', wincache_ucache_get('not_cake')); } /** * Tests that configuring groups for stored keys return the correct values when read/written * Shows that altering the group value is equivalent to deleting all keys under the same * group * * @return void */ public function testGroupsReadWrite() { Cache::config('wincache_groups', array( 'engine' => 'Wincache', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'wincache_groups')); $this->assertEquals('value', Cache::read('test_groups', 'wincache_groups')); wincache_ucache_inc('test_group_a'); $this->assertFalse(Cache::read('test_groups', 'wincache_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'wincache_groups')); $this->assertEquals('value2', Cache::read('test_groups', 'wincache_groups')); wincache_ucache_inc('test_group_b'); $this->assertFalse(Cache::read('test_groups', 'wincache_groups')); $this->assertTrue(Cache::write('test_groups', 'value3', 'wincache_groups')); $this->assertEquals('value3', Cache::read('test_groups', 'wincache_groups')); } /** * Tests that deleteing from a groups-enabled config is possible * * @return void */ public function testGroupDelete() { Cache::config('wincache_groups', array( 'engine' => 'Wincache', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'wincache_groups')); $this->assertEquals('value', Cache::read('test_groups', 'wincache_groups')); $this->assertTrue(Cache::delete('test_groups', 'wincache_groups')); $this->assertFalse(Cache::read('test_groups', 'wincache_groups')); } /** * Test clearing a cache group * * @return void */ public function testGroupClear() { Cache::config('wincache_groups', array( 'engine' => 'Wincache', 'duration' => 0, 'groups' => array('group_a', 'group_b'), 'prefix' => 'test_' )); $this->assertTrue(Cache::write('test_groups', 'value', 'wincache_groups')); $this->assertTrue(Cache::clearGroup('group_a', 'wincache_groups')); $this->assertFalse(Cache::read('test_groups', 'wincache_groups')); $this->assertTrue(Cache::write('test_groups', 'value2', 'wincache_groups')); $this->assertTrue(Cache::clearGroup('group_b', 'wincache_groups')); $this->assertFalse(Cache::read('test_groups', 'wincache_groups')); } /** * Test that failed add write return false. * * @return void */ public function testAdd() { Cache::delete('test_add_key', 'wincache'); $result = Cache::add('test_add_key', 'test data', 'wincache'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'wincache'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'wincache'); $this->assertFalse($result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Cache/CacheTest.php0000644000000000000000000003743413365153155022501 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Cache * @since CakePHP(tm) v 1.2.0.5432 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Cache', 'Cache'); /** * CacheTest class * * @package Cake.Test.Case.Cache */ class CacheTest extends CakeTestCase { protected $_count = 0; /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->_cacheDisable = Configure::read('Cache.disable'); Configure::write('Cache.disable', false); $this->_defaultCacheConfig = Cache::config('default'); Cache::config('default', array('engine' => 'File', 'path' => TMP . 'tests')); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Cache::drop('latest'); Cache::drop('page'); Cache::drop('archive'); Configure::write('Cache.disable', $this->_cacheDisable); Cache::config('default', $this->_defaultCacheConfig['settings']); } /** * testConfig method * * @return void */ public function testConfig() { $settings = array('engine' => 'File', 'path' => TMP . 'tests', 'prefix' => 'cake_test_'); $results = Cache::config('new', $settings); $this->assertEquals(Cache::config('new'), $results); $this->assertTrue(isset($results['engine'])); $this->assertTrue(isset($results['settings'])); } /** * testConfigInvalidEngine method * * @expectedException CacheException * @return void */ public function testConfigInvalidEngine() { $settings = array('engine' => 'Imaginary'); Cache::config('imaginary', $settings); } /** * Check that no fatal errors are issued doing normal things when Cache.disable is true. * * @return void */ public function testNonFatalErrorsWithCachedisable() { Configure::write('Cache.disable', true); Cache::config('test', array('engine' => 'File', 'path' => TMP, 'prefix' => 'error_test_')); Cache::write('no_save', 'Noooo!', 'test'); Cache::read('no_save', 'test'); Cache::delete('no_save', 'test'); Cache::set('duration', '+10 minutes'); Configure::write('Cache.disable', false); } /** * test configuring CacheEngines in App/libs * * @return void */ public function testConfigWithLibAndPluginEngines() { App::build(array( 'Lib' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Lib' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load('TestPlugin'); $settings = array('engine' => 'TestAppCache', 'path' => TMP, 'prefix' => 'cake_test_'); $result = Cache::config('libEngine', $settings); $this->assertEquals(Cache::config('libEngine'), $result); $settings = array('engine' => 'TestPlugin.TestPluginCache', 'path' => TMP, 'prefix' => 'cake_test_'); $result = Cache::config('pluginLibEngine', $settings); $this->assertEquals(Cache::config('pluginLibEngine'), $result); Cache::drop('libEngine'); Cache::drop('pluginLibEngine'); App::build(); CakePlugin::unload(); } /** * testInvalidConfig method * * Test that the cache class doesn't cause fatal errors with a partial path * * @expectedException PHPUnit_Framework_Error_Warning * @return void */ public function testInvalidConfig() { // In debug mode it would auto create the folder. $debug = Configure::read('debug'); Configure::write('debug', 0); Cache::config('invalid', array( 'engine' => 'File', 'duration' => '+1 year', 'prefix' => 'testing_invalid_', 'path' => 'data/', 'serialize' => true, 'random' => 'wii' )); Cache::read('Test', 'invalid'); Configure::write('debug', $debug); } /** * Test reading from a config that is undefined. * * @return void */ public function testReadNonExistingConfig() { $this->assertFalse(Cache::read('key', 'totally fake')); $this->assertFalse(Cache::write('key', 'value', 'totally fake')); $this->assertFalse(Cache::increment('key', 1, 'totally fake')); $this->assertFalse(Cache::decrement('key', 1, 'totally fake')); } /** * test that trying to configure classes that don't extend CacheEngine fail. * * @expectedException CacheException * @return void */ public function testAttemptingToConfigureANonCacheEngineClass() { $this->getMock('StdClass', array(), array(), 'RubbishEngine'); Cache::config('Garbage', array( 'engine' => 'Rubbish' )); } /** * testConfigChange method * * @return void */ public function testConfigChange() { $_cacheConfigSessions = Cache::config('sessions'); $_cacheConfigTests = Cache::config('tests'); $result = Cache::config('sessions', array('engine' => 'File', 'path' => TMP . 'sessions')); $this->assertEquals(Cache::settings('sessions'), $result['settings']); $result = Cache::config('tests', array('engine' => 'File', 'path' => TMP . 'tests')); $this->assertEquals(Cache::settings('tests'), $result['settings']); Cache::config('sessions', $_cacheConfigSessions['settings']); Cache::config('tests', $_cacheConfigTests['settings']); } /** * test that calling config() sets the 'default' configuration up. * * @return void */ public function testConfigSettingDefaultConfigKey() { Cache::config('test_name', array('engine' => 'File', 'prefix' => 'test_name_')); Cache::write('value_one', 'I am cached', 'test_name'); $result = Cache::read('value_one', 'test_name'); $this->assertEquals('I am cached', $result); $result = Cache::read('value_one'); $this->assertEquals(null, $result); Cache::write('value_one', 'I am in default config!'); $result = Cache::read('value_one'); $this->assertEquals('I am in default config!', $result); $result = Cache::read('value_one', 'test_name'); $this->assertEquals('I am cached', $result); Cache::delete('value_one', 'test_name'); Cache::delete('value_one', 'default'); } /** * testWritingWithConfig method * * @return void */ public function testWritingWithConfig() { $_cacheConfigSessions = Cache::config('sessions'); Cache::write('test_something', 'this is the test data', 'tests'); $expected = array( 'path' => TMP . 'sessions' . DS, 'prefix' => 'cake_', 'lock' => true, 'serialize' => true, 'duration' => 3600, 'probability' => 100, 'engine' => 'File', 'isWindows' => DIRECTORY_SEPARATOR === '\\', 'mask' => 0664, 'groups' => array() ); $this->assertEquals($expected, Cache::settings('sessions')); Cache::config('sessions', $_cacheConfigSessions['settings']); } /** * testGroupConfigs method * * @return void */ public function testGroupConfigs() { Cache::config('latest', array( 'duration' => 300, 'engine' => 'File', 'groups' => array( 'posts', 'comments', ), )); $expected = array( 'posts' => array('latest'), 'comments' => array('latest'), ); $result = Cache::groupConfigs(); $this->assertEquals($expected, $result); $result = Cache::groupConfigs('posts'); $this->assertEquals(array('posts' => array('latest')), $result); Cache::config('page', array( 'duration' => 86400, 'engine' => 'File', 'groups' => array( 'posts', 'archive' ), )); $result = Cache::groupConfigs(); $expected = array( 'posts' => array('latest', 'page'), 'comments' => array('latest'), 'archive' => array('page'), ); $this->assertEquals($expected, $result); $result = Cache::groupConfigs('archive'); $this->assertEquals(array('archive' => array('page')), $result); Cache::config('archive', array( 'duration' => 86400 * 30, 'engine' => 'File', 'groups' => array( 'posts', 'archive', 'comments', ), )); $result = Cache::groupConfigs('archive'); $this->assertEquals(array('archive' => array('archive', 'page')), $result); } /** * testGroupConfigsThrowsException method * * @expectedException CacheException * @return void */ public function testGroupConfigsThrowsException() { Cache::groupConfigs('bogus'); } /** * test that configured returns an array of the currently configured cache * settings * * @return void */ public function testConfigured() { $result = Cache::configured(); $this->assertTrue(in_array('_cake_core_', $result)); $this->assertTrue(in_array('default', $result)); } /** * testInitSettings method * * @return void */ public function testInitSettings() { $initial = Cache::settings(); $override = array('engine' => 'File', 'path' => TMP . 'tests'); Cache::config('for_test', $override); $settings = Cache::settings(); $expecting = $override + $initial; $this->assertEquals($settings, $expecting); } /** * test that drop removes cache configs, and that further attempts to use that config * do not work. * * @return void */ public function testDrop() { App::build(array( 'Lib' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Lib' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); $result = Cache::drop('some_config_that_does_not_exist'); $this->assertFalse($result); $_testsConfig = Cache::config('tests'); $result = Cache::drop('tests'); $this->assertTrue($result); Cache::config('unconfigTest', array( 'engine' => 'TestAppCache' )); $this->assertTrue(Cache::isInitialized('unconfigTest')); $this->assertTrue(Cache::drop('unconfigTest')); $this->assertFalse(Cache::isInitialized('TestAppCache')); Cache::config('tests', $_testsConfig); App::build(); } /** * testWriteEmptyValues method * * @return void */ public function testWriteEmptyValues() { Cache::write('App.falseTest', false); $this->assertFalse(Cache::read('App.falseTest')); Cache::write('App.trueTest', true); $this->assertTrue(Cache::read('App.trueTest')); Cache::write('App.nullTest', null); $this->assertNull(Cache::read('App.nullTest')); Cache::write('App.zeroTest', 0); $this->assertSame(Cache::read('App.zeroTest'), 0); Cache::write('App.zeroTest2', '0'); $this->assertSame(Cache::read('App.zeroTest2'), '0'); } /** * Test that failed writes cause errors to be triggered. * * @return void */ public function testWriteTriggerError() { App::build(array( 'Lib' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Lib' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); Cache::config('test_trigger', array('engine' => 'TestAppCache', 'prefix' => '')); try { Cache::write('fail', 'value', 'test_trigger'); $this->fail('No exception thrown'); } catch (PHPUnit_Framework_Error $e) { $this->assertTrue(true); } Cache::drop('test_trigger'); App::build(); } /** * testCacheDisable method * * Check that the "Cache.disable" configuration and a change to it * (even after a cache config has been setup) is taken into account. * * @return void */ public function testCacheDisable() { Configure::write('Cache.disable', false); Cache::config('test_cache_disable_1', array('engine' => 'File', 'path' => TMP . 'tests')); $this->assertTrue(Cache::write('key_1', 'hello', 'test_cache_disable_1')); $this->assertSame(Cache::read('key_1', 'test_cache_disable_1'), 'hello'); Configure::write('Cache.disable', true); $this->assertFalse(Cache::write('key_2', 'hello', 'test_cache_disable_1')); $this->assertFalse(Cache::read('key_2', 'test_cache_disable_1')); Configure::write('Cache.disable', false); $this->assertTrue(Cache::write('key_3', 'hello', 'test_cache_disable_1')); $this->assertSame(Cache::read('key_3', 'test_cache_disable_1'), 'hello'); Configure::write('Cache.disable', true); Cache::config('test_cache_disable_2', array('engine' => 'File', 'path' => TMP . 'tests')); $this->assertFalse(Cache::write('key_4', 'hello', 'test_cache_disable_2')); $this->assertFalse(Cache::read('key_4', 'test_cache_disable_2')); Configure::write('Cache.disable', false); $this->assertTrue(Cache::write('key_5', 'hello', 'test_cache_disable_2')); $this->assertSame(Cache::read('key_5', 'test_cache_disable_2'), 'hello'); Configure::write('Cache.disable', true); $this->assertFalse(Cache::write('key_6', 'hello', 'test_cache_disable_2')); $this->assertFalse(Cache::read('key_6', 'test_cache_disable_2')); } /** * testSet method * * @return void */ public function testSet() { $_cacheSet = Cache::set(); Cache::set(array('duration' => '+1 year')); $data = Cache::read('test_cache'); $this->assertFalse($data); $data = 'this is just a simple test of the cache system'; $write = Cache::write('test_cache', $data); $this->assertTrue($write); Cache::set(array('duration' => '+1 year')); $data = Cache::read('test_cache'); $this->assertEquals('this is just a simple test of the cache system', $data); Cache::delete('test_cache'); Cache::settings(); Cache::set($_cacheSet); } /** * test set() parameter handling for user cache configs. * * @return void */ public function testSetOnAlternateConfigs() { Cache::config('file_config', array('engine' => 'File', 'prefix' => 'test_file_')); Cache::set(array('duration' => '+1 year'), 'file_config'); $settings = Cache::settings('file_config'); $this->assertEquals('test_file_', $settings['prefix']); $this->assertEquals(strtotime('+1 year') - time(), $settings['duration']); } /** * test remember method. * * @return void */ public function testRemember() { $expected = 'This is some data 0'; $result = Cache::remember('test_key', array($this, 'cacher'), 'default'); $this->assertEquals($expected, $result); $this->_count = 1; $result = Cache::remember('test_key', array($this, 'cacher'), 'default'); $this->assertEquals($expected, $result); } /** * Method for testing Cache::remember() * * @return string */ public function cacher() { return 'This is some data ' . $this->_count; } /** * Test add method. * * @return void */ public function testAdd() { Cache::delete('test_add_key', 'default'); $result = Cache::add('test_add_key', 'test data', 'default'); $this->assertTrue($result); $expected = 'test data'; $result = Cache::read('test_add_key', 'default'); $this->assertEquals($expected, $result); $result = Cache::add('test_add_key', 'test data 2', 'default'); $this->assertFalse($result); } /** * Test engine method. * * Success, default engine. * * @return void */ public function testEngineSuccess() { $actual = Cache::engine(); $this->assertInstanceOf('CacheEngine', $actual); $actual = Cache::engine('default'); $this->assertInstanceOf('CacheEngine', $actual); } /** * Test engine method. * * Success, memcached engine. * * @return void */ public function testEngineSuccessMemcached() { $this->skipIf(!class_exists('Memcached'), 'Memcached is not installed or configured properly.'); // @codingStandardsIgnoreStart $socket = @fsockopen('127.0.0.1', 11211, $errno, $errstr, 1); // @codingStandardsIgnoreEnd $this->skipIf(!$socket, 'Memcached is not running.'); fclose($socket); Cache::config('memcached', array( 'engine' => 'Memcached', 'prefix' => 'cake_', 'duration' => 3600 )); $actual = Cache::engine('memcached'); $this->assertInstanceOf('MemcachedEngine', $actual); $this->assertTrue($actual->add('test_add_key', 'test data', 10)); $this->assertFalse($actual->add('test_add_key', 'test data', 10)); $this->assertTrue($actual->delete('test_add_key')); } /** * Test engine method. * * Failure. * * @return void */ public function testEngineFailure() { $actual = Cache::engine('some_config_that_does_not_exist'); $this->assertNull($actual); Configure::write('Cache.disable', true); $actual = Cache::engine(); $this->assertNull($actual); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/0000755000000000000000000000000013365153155020536 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/Route/0000755000000000000000000000000013365153155021634 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/Route/CakeRouteTest.php0000644000000000000000000007726413365153155025107 0ustar rootroot null, 'prefixes' => array())); } /** * Test the construction of a CakeRoute * * @return void */ public function testConstruction() { $route = new CakeRoute('/:controller/:action/:id', array(), array('id' => '[0-9]+')); $this->assertEquals('/:controller/:action/:id', $route->template); $this->assertEquals(array(), $route->defaults); $this->assertEquals(array('id' => '[0-9]+'), $route->options); $this->assertFalse($route->compiled()); } /** * test Route compiling. * * @return void */ public function testBasicRouteCompiling() { $route = new CakeRoute('/', array('controller' => 'pages', 'action' => 'display', 'home')); $result = $route->compile(); $expected = '#^/*$#'; $this->assertEquals($expected, $result); $this->assertEquals(array(), $route->keys); $route = new CakeRoute('/:controller/:action', array('controller' => 'posts')); $result = $route->compile(); $this->assertRegExp($result, '/posts/edit'); $this->assertRegExp($result, '/posts/super_delete'); $this->assertNotRegExp($result, '/posts'); $this->assertNotRegExp($result, '/posts/super_delete/1'); $route = new CakeRoute('/posts/foo:id', array('controller' => 'posts', 'action' => 'view')); $result = $route->compile(); $this->assertRegExp($result, '/posts/foo:1'); $this->assertRegExp($result, '/posts/foo:param'); $this->assertNotRegExp($result, '/posts'); $this->assertNotRegExp($result, '/posts/'); $this->assertEquals(array('id'), $route->keys); $route = new CakeRoute('/:plugin/:controller/:action/*', array('plugin' => 'test_plugin', 'action' => 'index')); $result = $route->compile(); $this->assertRegExp($result, '/test_plugin/posts/index'); $this->assertRegExp($result, '/test_plugin/posts/edit/5'); $this->assertRegExp($result, '/test_plugin/posts/edit/5/name:value/nick:name'); } /** * test that route parameters that overlap don't cause errors. * * @return void */ public function testRouteParameterOverlap() { $route = new CakeRoute('/invoices/add/:idd/:id', array('controller' => 'invoices', 'action' => 'add')); $result = $route->compile(); $this->assertRegExp($result, '/invoices/add/1/3'); $route = new CakeRoute('/invoices/add/:id/:idd', array('controller' => 'invoices', 'action' => 'add')); $result = $route->compile(); $this->assertRegExp($result, '/invoices/add/1/3'); } /** * test compiling routes with keys that have patterns * * @return void */ public function testRouteCompilingWithParamPatterns() { $route = new CakeRoute( '/:controller/:action/:id', array(), array('id' => Router::ID) ); $result = $route->compile(); $this->assertRegExp($result, '/posts/edit/1'); $this->assertRegExp($result, '/posts/view/518098'); $this->assertNotRegExp($result, '/posts/edit/name-of-post'); $this->assertNotRegExp($result, '/posts/edit/4/other:param'); $this->assertEquals(array('id', 'controller', 'action'), $route->keys); $route = new CakeRoute( '/:lang/:controller/:action/:id', array('controller' => 'testing4'), array('id' => Router::ID, 'lang' => '[a-z]{3}') ); $result = $route->compile(); $this->assertRegExp($result, '/eng/posts/edit/1'); $this->assertRegExp($result, '/cze/articles/view/1'); $this->assertNotRegExp($result, '/language/articles/view/2'); $this->assertNotRegExp($result, '/eng/articles/view/name-of-article'); $this->assertEquals(array('lang', 'id', 'controller', 'action'), $route->keys); foreach (array(':', '@', ';', '$', '-') as $delim) { $route = new CakeRoute('/posts/:id' . $delim . ':title'); $result = $route->compile(); $this->assertRegExp($result, '/posts/1' . $delim . 'name-of-article'); $this->assertRegExp($result, '/posts/13244' . $delim . 'name-of_Article[]'); $this->assertNotRegExp($result, '/posts/11!nameofarticle'); $this->assertNotRegExp($result, '/posts/11'); $this->assertEquals(array('title', 'id'), $route->keys); } $route = new CakeRoute( '/posts/:id::title/:year', array('controller' => 'posts', 'action' => 'view'), array('id' => Router::ID, 'year' => Router::YEAR, 'title' => '[a-z-_]+') ); $result = $route->compile(); $this->assertRegExp($result, '/posts/1:name-of-article/2009/'); $this->assertRegExp($result, '/posts/13244:name-of-article/1999'); $this->assertNotRegExp($result, '/posts/hey_now:nameofarticle'); $this->assertNotRegExp($result, '/posts/:nameofarticle/2009'); $this->assertNotRegExp($result, '/posts/:nameofarticle/01'); $this->assertEquals(array('year', 'title', 'id'), $route->keys); $route = new CakeRoute( '/posts/:url_title-(uuid::id)', array('controller' => 'posts', 'action' => 'view'), array('pass' => array('id', 'url_title'), 'id' => Router::ID) ); $result = $route->compile(); $this->assertRegExp($result, '/posts/some_title_for_article-(uuid:12534)/'); $this->assertRegExp($result, '/posts/some_title_for_article-(uuid:12534)'); $this->assertNotRegExp($result, '/posts/'); $this->assertNotRegExp($result, '/posts/nameofarticle'); $this->assertNotRegExp($result, '/posts/nameofarticle-12347'); $this->assertEquals(array('url_title', 'id'), $route->keys); } /** * test more complex route compiling & parsing with mid route greedy stars * and optional routing parameters * * @return void */ public function testComplexRouteCompilingAndParsing() { $route = new CakeRoute( '/posts/:month/:day/:year/*', array('controller' => 'posts', 'action' => 'view'), array('year' => Router::YEAR, 'month' => Router::MONTH, 'day' => Router::DAY) ); $result = $route->compile(); $this->assertRegExp($result, '/posts/08/01/2007/title-of-post'); $result = $route->parse('/posts/08/01/2007/title-of-post'); $this->assertEquals(7, count($result)); $this->assertEquals('posts', $result['controller']); $this->assertEquals('view', $result['action']); $this->assertEquals('2007', $result['year']); $this->assertEquals('08', $result['month']); $this->assertEquals('01', $result['day']); $this->assertEquals('title-of-post', $result['pass'][0]); $route = new CakeRoute( "/:extra/page/:slug/*", array('controller' => 'pages', 'action' => 'view', 'extra' => null), array("extra" => '[a-z1-9_]*', "slug" => '[a-z1-9_]+', "action" => 'view') ); $result = $route->compile(); $this->assertRegExp($result, '/some_extra/page/this_is_the_slug'); $this->assertRegExp($result, '/page/this_is_the_slug'); $this->assertEquals(array('slug', 'extra'), $route->keys); $this->assertEquals(array('extra' => '[a-z1-9_]*', 'slug' => '[a-z1-9_]+', 'action' => 'view'), $route->options); $expected = array( 'controller' => 'pages', 'action' => 'view' ); $this->assertEquals($expected, $route->defaults); $route = new CakeRoute( '/:controller/:action/*', array('project' => false), array( 'controller' => 'source|wiki|commits|tickets|comments|view', 'action' => 'branches|history|branch|logs|view|start|add|edit|modify' ) ); $this->assertFalse($route->parse('/chaw_test/wiki')); $result = $route->compile(); $this->assertNotRegExp($result, '/some_project/source'); $this->assertRegExp($result, '/source/view'); $this->assertRegExp($result, '/source/view/other/params'); $this->assertNotRegExp($result, '/chaw_test/wiki'); $this->assertNotRegExp($result, '/source/wierd_action'); } /** * test that routes match their pattern. * * @return void */ public function testMatchBasic() { $route = new CakeRoute('/:controller/:action/:id', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null)); $this->assertFalse($result); $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 0)); $this->assertFalse($result); $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 1)); $this->assertEquals('/posts/view/1', $result); $route = new CakeRoute('/', array('controller' => 'pages', 'action' => 'display', 'home')); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'home')); $this->assertEquals('/', $result); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'about')); $this->assertFalse($result); $route = new CakeRoute('/pages/*', array('controller' => 'pages', 'action' => 'display')); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'home')); $this->assertEquals('/pages/home', $result); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'about')); $this->assertEquals('/pages/about', $result); $route = new CakeRoute('/blog/:action', array('controller' => 'posts')); $result = $route->match(array('controller' => 'posts', 'action' => 'view')); $this->assertEquals('/blog/view', $result); $result = $route->match(array('controller' => 'nodes', 'action' => 'view')); $this->assertFalse($result); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 1)); $this->assertFalse($result); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'id' => 2)); $this->assertFalse($result); $route = new CakeRoute('/foo/:controller/:action', array('action' => 'index')); $result = $route->match(array('controller' => 'posts', 'action' => 'view')); $this->assertEquals('/foo/posts/view', $result); $route = new CakeRoute('/:plugin/:id/*', array('controller' => 'posts', 'action' => 'view')); $result = $route->match(array('plugin' => 'test', 'controller' => 'posts', 'action' => 'view', 'id' => '1')); $this->assertEquals('/test/1/', $result); $result = $route->match(array('plugin' => 'fo', 'controller' => 'posts', 'action' => 'view', 'id' => '1', '0')); $this->assertEquals('/fo/1/0', $result); $result = $route->match(array('plugin' => 'fo', 'controller' => 'nodes', 'action' => 'view', 'id' => 1)); $this->assertFalse($result); $result = $route->match(array('plugin' => 'fo', 'controller' => 'posts', 'action' => 'edit', 'id' => 1)); $this->assertFalse($result); $route = new CakeRoute('/admin/subscriptions/:action/*', array( 'controller' => 'subscribe', 'admin' => true, 'prefix' => 'admin' )); $url = array('controller' => 'subscribe', 'admin' => true, 'action' => 'edit', 1); $result = $route->match($url); $expected = '/admin/subscriptions/edit/1'; $this->assertEquals($expected, $result); $url = array( 'controller' => 'subscribe', 'admin' => true, 'action' => 'edit_admin_e', 1 ); $result = $route->match($url); $expected = '/admin/subscriptions/edit_admin_e/1'; $this->assertEquals($expected, $result); $url = array('controller' => 'subscribe', 'admin' => true, 'action' => 'admin_edit', 1); $result = $route->match($url); $expected = '/admin/subscriptions/edit/1'; $this->assertEquals($expected, $result); } /** * test that non-greedy routes fail with extra passed args * * @return void */ public function testGreedyRouteFailurePassedArg() { $route = new CakeRoute('/:controller/:action', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'view', '0')); $this->assertFalse($result); $route = new CakeRoute('/:controller/:action', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'test')); $this->assertFalse($result); } /** * test that non-greedy routes fail with extra passed args * * @return void */ public function testGreedyRouteFailureNamedParam() { $route = new CakeRoute('/:controller/:action', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'page' => 1)); $this->assertFalse($result); } /** * test that falsey values do not interrupt a match. * * @return void */ public function testMatchWithFalseyValues() { $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); $result = $route->match(array( 'controller' => 'posts', 'action' => 'index', 'plugin' => null, 'admin' => false )); $this->assertEquals('/posts/index/', $result); } /** * test match() with greedy routes, named parameters and passed args. * * @return void */ public function testMatchWithNamedParametersAndPassedArgs() { Router::connectNamed(true); $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'plugin' => null, 'page' => 1)); $this->assertEquals('/posts/index/page:1', $result); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5)); $this->assertEquals('/posts/view/5', $result); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 0)); $this->assertEquals('/posts/view/0', $result); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, '0')); $this->assertEquals('/posts/view/0', $result); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, 'page' => 1, 'limit' => 20, 'order' => 'title')); $this->assertEquals('/posts/view/5/page:1/limit:20/order:title', $result); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 'word space', 'order' => 'ฮ˜')); $this->assertEquals('/posts/view/word%20space/order:%CE%98', $result); $route = new CakeRoute('/test2/*', array('controller' => 'pages', 'action' => 'display', 2)); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 1)); $this->assertFalse($result); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 2, 'something')); $this->assertEquals('/test2/something', $result); $result = $route->match(array('controller' => 'pages', 'action' => 'display', 5, 'something')); $this->assertFalse($result); } /** * Ensure that named parameters are urldecoded * * @return void */ public function testParseNamedParametersUrlDecode() { Router::connectNamed(true); $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); $result = $route->parse('/posts/index/page:%CE%98'); $this->assertEquals('ฮ˜', $result['named']['page']); $result = $route->parse('/posts/index/page[]:%CE%98'); $this->assertEquals('ฮ˜', $result['named']['page'][0]); $result = $route->parse('/posts/index/something%20else/page[]:%CE%98'); $this->assertEquals('ฮ˜', $result['named']['page'][0]); $this->assertEquals('something else', $result['pass'][0]); } /** * Ensure that keys at named parameters are urldecoded * * @return void */ public function testParseNamedKeyUrlDecode() { Router::connectNamed(true); $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); // checking /post/index/user[0]:a/user[1]:b $result = $route->parse('/posts/index/user%5B0%5D:a/user%5B1%5D:b'); $this->assertArrayHasKey('user', $result['named']); $this->assertEquals(array('a', 'b'), $result['named']['user']); // checking /post/index/user[]:a/user[]:b $result = $route->parse('/posts/index/user%5B%5D:a/user%5B%5D:b'); $this->assertArrayHasKey('user', $result['named']); $this->assertEquals(array('a', 'b'), $result['named']['user']); } /** * test that named params with null/false are excluded * * @return void */ public function testNamedParamsWithNullFalse() { $route = new CakeRoute('/:controller/:action/*'); $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'page' => null, 'sort' => false)); $this->assertEquals('/posts/index/', $result); } /** * test that match with patterns works. * * @return void */ public function testMatchWithPatterns() { $route = new CakeRoute('/:controller/:action/:id', array('plugin' => null), array('id' => '[0-9]+')); $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'id' => 'foo')); $this->assertFalse($result); $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => '9')); $this->assertEquals('/posts/view/9', $result); $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => '922')); $this->assertEquals('/posts/view/922', $result); $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 'a99')); $this->assertFalse($result); } /** * test persistParams ability to persist parameters from $params and remove params. * * @return void */ public function testPersistParams() { $route = new CakeRoute( '/:lang/:color/blog/:action', array('controller' => 'posts'), array('persist' => array('lang', 'color')) ); $url = array('controller' => 'posts', 'action' => 'index'); $params = array('lang' => 'en', 'color' => 'blue'); $result = $route->persistParams($url, $params); $this->assertEquals('en', $result['lang']); $this->assertEquals('blue', $result['color']); $url = array('controller' => 'posts', 'action' => 'index', 'color' => 'red'); $params = array('lang' => 'en', 'color' => 'blue'); $result = $route->persistParams($url, $params); $this->assertEquals('en', $result['lang']); $this->assertEquals('red', $result['color']); } /** * test persist with a non array value * * @return void */ public function testPersistParamsNonArray() { $url = array('controller' => 'posts', 'action' => 'index'); $params = array('lang' => 'en', 'color' => 'blue'); $route = new CakeRoute( '/:lang/:color/blog/:action', array('controller' => 'posts') // No persist options ); $result = $route->persistParams($url, $params); $this->assertEquals($url, $result); $route = new CakeRoute( '/:lang/:color/blog/:action', array('controller' => 'posts'), array('persist' => false) ); $result = $route->persistParams($url, $params); $this->assertEquals($url, $result); $route = new CakeRoute( '/:lang/:color/blog/:action', array('controller' => 'posts'), array('persist' => 'derp') ); $result = $route->persistParams($url, $params); $this->assertEquals($url, $result); } /** * test the parse method of CakeRoute. * * @return void */ public function testParse() { $route = new CakeRoute( '/:controller/:action/:id', array('controller' => 'testing4', 'id' => null), array('id' => Router::ID) ); $route->compile(); $result = $route->parse('/posts/view/1'); $this->assertEquals('posts', $result['controller']); $this->assertEquals('view', $result['action']); $this->assertEquals('1', $result['id']); $route = new Cakeroute( '/admin/:controller', array('prefix' => 'admin', 'admin' => 1, 'action' => 'index') ); $route->compile(); $result = $route->parse('/admin/'); $this->assertFalse($result); $result = $route->parse('/admin/posts'); $this->assertEquals('posts', $result['controller']); $this->assertEquals('index', $result['action']); } /** * Test that :key elements are urldecoded * * @return void */ public function testParseUrlDecodeElements() { $route = new Cakeroute( '/:controller/:slug', array('action' => 'view') ); $route->compile(); $result = $route->parse('/posts/%E2%88%82%E2%88%82'); $this->assertEquals('posts', $result['controller']); $this->assertEquals('view', $result['action']); $this->assertEquals('โˆ‚โˆ‚', $result['slug']); $result = $route->parse('/posts/โˆ‚โˆ‚'); $this->assertEquals('posts', $result['controller']); $this->assertEquals('view', $result['action']); $this->assertEquals('โˆ‚โˆ‚', $result['slug']); } /** * test numerically indexed defaults, get appended to pass * * @return void */ public function testParseWithPassDefaults() { $route = new Cakeroute('/:controller', array('action' => 'display', 'home')); $result = $route->parse('/posts'); $expected = array( 'controller' => 'posts', 'action' => 'display', 'pass' => array('home'), 'named' => array() ); $this->assertEquals($expected, $result); } /** * test that http header conditions can cause route failures. * * @return void */ public function testParseWithHttpHeaderConditions() { $_SERVER['REQUEST_METHOD'] = 'GET'; $route = new CakeRoute('/sample', array('controller' => 'posts', 'action' => 'index', '[method]' => 'POST')); $this->assertFalse($route->parse('/sample')); } /** * test that patterns work for :action * * @return void */ public function testPatternOnAction() { $route = new CakeRoute( '/blog/:action/*', array('controller' => 'blog_posts'), array('action' => 'other|actions') ); $result = $route->match(array('controller' => 'blog_posts', 'action' => 'foo')); $this->assertFalse($result); $result = $route->match(array('controller' => 'blog_posts', 'action' => 'actions')); $this->assertNotEmpty($result); $result = $route->parse('/blog/other'); $expected = array('controller' => 'blog_posts', 'action' => 'other', 'pass' => array(), 'named' => array()); $this->assertEquals($expected, $result); $result = $route->parse('/blog/foobar'); $this->assertFalse($result); } /** * test the parseArgs method * * @return void */ public function testParsePassedArgument() { $route = new CakeRoute('/:controller/:action/*'); $result = $route->parse('/posts/edit/1/2/0'); $expected = array( 'controller' => 'posts', 'action' => 'edit', 'pass' => array('1', '2', '0'), 'named' => array() ); $this->assertEquals($expected, $result); $result = $route->parse('/posts/edit/a-string/page:1/sort:value'); $expected = array( 'controller' => 'posts', 'action' => 'edit', 'pass' => array('a-string'), 'named' => array( 'page' => 1, 'sort' => 'value' ) ); $this->assertEquals($expected, $result); } /** * test that only named parameter rules are followed. * * @return void */ public function testParseNamedParametersWithRules() { $route = new CakeRoute('/:controller/:action/*', array(), array( 'named' => array( 'wibble', 'fish' => array('action' => 'index'), 'fizz' => array('controller' => array('comments', 'other')), 'pattern' => 'val-[\d]+' ) )); $result = $route->parse('/posts/display/wibble:spin/fish:trout/fizz:buzz/unknown:value'); $expected = array( 'controller' => 'posts', 'action' => 'display', 'pass' => array('fish:trout', 'fizz:buzz', 'unknown:value'), 'named' => array( 'wibble' => 'spin' ) ); $this->assertEquals($expected, $result, 'Fish should not be parsed, as action != index'); $result = $route->parse('/posts/index/wibble:spin/fish:trout/fizz:buzz'); $expected = array( 'controller' => 'posts', 'action' => 'index', 'pass' => array('fizz:buzz'), 'named' => array( 'wibble' => 'spin', 'fish' => 'trout' ) ); $this->assertEquals($expected, $result, 'Fizz should be parsed, as controller == comments|other'); $result = $route->parse('/comments/index/wibble:spin/fish:trout/fizz:buzz'); $expected = array( 'controller' => 'comments', 'action' => 'index', 'pass' => array(), 'named' => array( 'wibble' => 'spin', 'fish' => 'trout', 'fizz' => 'buzz' ) ); $this->assertEquals($expected, $result, 'All params should be parsed as conditions were met.'); $result = $route->parse('/comments/index/pattern:val--'); $expected = array( 'controller' => 'comments', 'action' => 'index', 'pass' => array('pattern:val--'), 'named' => array() ); $this->assertEquals($expected, $result, 'Named parameter pattern unmet.'); $result = $route->parse('/comments/index/pattern:val-2'); $expected = array( 'controller' => 'comments', 'action' => 'index', 'pass' => array(), 'named' => array('pattern' => 'val-2') ); $this->assertEquals($expected, $result, 'Named parameter pattern met.'); } /** * test that greedyNamed ignores rules. * * @return void */ public function testParseGreedyNamed() { $route = new CakeRoute('/:controller/:action/*', array(), array( 'named' => array( 'fizz' => array('controller' => 'comments'), 'pattern' => 'val-[\d]+', ), 'greedyNamed' => true )); $result = $route->parse('/posts/display/wibble:spin/fizz:buzz/pattern:ignored'); $expected = array( 'controller' => 'posts', 'action' => 'display', 'pass' => array('fizz:buzz', 'pattern:ignored'), 'named' => array( 'wibble' => 'spin', ) ); $this->assertEquals($expected, $result, 'Greedy named grabs everything, rules are followed'); } /** * Having greedNamed enabled should not capture routing.prefixes. * * @return void */ public function testMatchGreedyNamedExcludesPrefixes() { Configure::write('Routing.prefixes', array('admin')); Router::reload(); $route = new CakeRoute('/sales/*', array('controller' => 'sales', 'action' => 'index')); $this->assertFalse($route->match(array('controller' => 'sales', 'action' => 'index', 'admin' => 1)), 'Greedy named consume routing prefixes.'); } /** * test that parsing array format named parameters works * * @return void */ public function testParseArrayNamedParameters() { $route = new CakeRoute('/:controller/:action/*'); $result = $route->parse('/tests/action/var[]:val1/var[]:val2'); $expected = array( 'controller' => 'tests', 'action' => 'action', 'named' => array( 'var' => array( 'val1', 'val2' ) ), 'pass' => array(), ); $this->assertEquals($expected, $result); $result = $route->parse('/tests/action/theanswer[is]:42/var[]:val2/var[]:val3'); $expected = array( 'controller' => 'tests', 'action' => 'action', 'named' => array( 'theanswer' => array( 'is' => 42 ), 'var' => array( 'val2', 'val3' ) ), 'pass' => array(), ); $this->assertEquals($expected, $result); $result = $route->parse('/tests/action/theanswer[is][not]:42/theanswer[]:5/theanswer[is]:6'); $expected = array( 'controller' => 'tests', 'action' => 'action', 'named' => array( 'theanswer' => array( 5, 'is' => array( 6, 'not' => 42 ) ), ), 'pass' => array(), ); $this->assertEquals($expected, $result); } /** * Test that match can handle array named parameters * * @return void */ public function testMatchNamedParametersArray() { $route = new CakeRoute('/:controller/:action/*'); $url = array( 'controller' => 'posts', 'action' => 'index', 'filter' => array( 'one', 'model' => 'value' ) ); $result = $route->match($url); $expected = '/posts/index/filter%5B0%5D:one/filter%5Bmodel%5D:value'; $this->assertEquals($expected, $result); $url = array( 'controller' => 'posts', 'action' => 'index', 'filter' => array( 'one', 'model' => array( 'two', 'order' => 'field' ) ) ); $result = $route->match($url); $expected = '/posts/index/filter%5B0%5D:one/filter%5Bmodel%5D%5B0%5D:two/filter%5Bmodel%5D%5Border%5D:field'; $this->assertEquals($expected, $result); } /** * Test matching of parameters where one parameter name starts with another parameter name * * @return void */ public function testMatchSimilarParameters() { $route = new CakeRoute('/:thisParam/:thisParamIsLonger'); $url = array( 'thisParamIsLonger' => 'bar', 'thisParam' => 'foo', ); $result = $route->match($url); $expected = '/foo/bar'; $this->assertEquals($expected, $result); } /** * Test match() with trailing ** style routes. * * @return void */ public function testMatchTrailing() { $route = new CakeRoute('/pages/**', array('controller' => 'pages', 'action' => 'display')); $id = 'test/ spaces/ๆผขๅญ—/laโ€ รฎn'; $result = $route->match(array( 'controller' => 'pages', 'action' => 'display', $id )); $expected = '/pages/test/%20spaces/%E6%BC%A2%E5%AD%97/la%E2%80%A0%C3%AEn'; $this->assertEquals($expected, $result); } /** * test restructuring args with pass key * * @return void */ public function testPassArgRestructure() { $route = new CakeRoute('/:controller/:action/:slug', array(), array( 'pass' => array('slug') )); $result = $route->parse('/posts/view/my-title'); $expected = array( 'controller' => 'posts', 'action' => 'view', 'slug' => 'my-title', 'pass' => array('my-title'), 'named' => array() ); $this->assertEquals($expected, $result, 'Slug should have moved'); } /** * Test the /** special type on parsing. * * @return void */ public function testParseTrailing() { $route = new CakeRoute('/:controller/:action/**'); $result = $route->parse('/posts/index/1/2/3/foo:bar'); $expected = array( 'controller' => 'posts', 'action' => 'index', 'pass' => array('1/2/3/foo:bar'), 'named' => array() ); $this->assertEquals($expected, $result); $result = $route->parse('/posts/index/http://example.com'); $expected = array( 'controller' => 'posts', 'action' => 'index', 'pass' => array('http://example.com'), 'named' => array() ); $this->assertEquals($expected, $result); } /** * Test the /** special type on parsing - UTF8. * * @return void */ public function testParseTrailingUTF8() { $route = new CakeRoute('/category/**', array('controller' => 'categories', 'action' => 'index')); $result = $route->parse('/category/%D9%85%D9%88%D8%A8%D8%A7%DB%8C%D9%84'); $expected = array( 'controller' => 'categories', 'action' => 'index', 'pass' => array('ู…ูˆุจุงŒู„'), 'named' => array() ); $this->assertEquals($expected, $result); } /** * test that utf-8 patterns work for :section * * @return void */ public function testUTF8PatternOnSection() { $route = new CakeRoute( '/:section', array('plugin' => 'blogs', 'controller' => 'posts', 'action' => 'index'), array( 'persist' => array('section'), 'section' => 'ุขู…ูˆุฒุด|weblog' ) ); $result = $route->parse('/%D8%A2%D9%85%D9%88%D8%B2%D8%B4'); $expected = array('section' => 'ุขู…ูˆุฒุด', 'plugin' => 'blogs', 'controller' => 'posts', 'action' => 'index', 'pass' => array(), 'named' => array()); $this->assertEquals($expected, $result); $result = $route->parse('/weblog'); $expected = array('section' => 'weblog', 'plugin' => 'blogs', 'controller' => 'posts', 'action' => 'index', 'pass' => array(), 'named' => array()); $this->assertEquals($expected, $result); } /** * Test for __set_state magic method on CakeRoute * * @return void */ public function testSetState() { $route = CakeRoute::__set_state(array( 'keys' => array(), 'options' => array(), 'defaults' => array( 'controller' => 'pages', 'action' => 'display', 'home', ), 'template' => '/', '_greedy' => false, '_compiledRoute' => null, '_headerMap' => array ( 'type' => 'content_type', 'method' => 'request_method', 'server' => 'server_name', ), )); $this->assertInstanceOf('CakeRoute', $route); $this->assertSame('/', $route->match(array('controller' => 'pages', 'action' => 'display', 'home'))); $this->assertFalse($route->match(array('controller' => 'pages', 'action' => 'display', 'about'))); $expected = array('controller' => 'pages', 'action' => 'display', 'pass' => array('home'), 'named' => array()); $this->assertEquals($expected, $route->parse('/')); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/Route/RedirectRouteTest.php0000644000000000000000000001216113365153155025766 0ustar rootroot null, 'prefixes' => array())); Router::reload(); } /** * test the parsing of routes. * * @return void */ public function testParsing() { $route = new RedirectRoute('/home', array('controller' => 'posts')); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/home'); $header = $route->response->header(); $this->assertEquals(Router::url('/posts', true), $header['Location']); $route = new RedirectRoute('/home', array('controller' => 'posts', 'action' => 'index')); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/home'); $header = $route->response->header(); $this->assertEquals(Router::url('/posts', true), $header['Location']); $this->assertEquals(301, $route->response->statusCode()); $route = new RedirectRoute('/google', 'http://google.com'); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/google'); $header = $route->response->header(); $this->assertEquals('http://google.com', $header['Location']); $route = new RedirectRoute('/posts/*', array('controller' => 'posts', 'action' => 'view'), array('status' => 302)); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/posts/2'); $header = $route->response->header(); $this->assertEquals(Router::url('/posts/view', true), $header['Location']); $this->assertEquals(302, $route->response->statusCode()); $route = new RedirectRoute('/posts/*', array('controller' => 'posts', 'action' => 'view'), array('persist' => true)); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/posts/2'); $header = $route->response->header(); $this->assertEquals(Router::url('/posts/view/2', true), $header['Location']); $route = new RedirectRoute('/posts/*', '/test', array('persist' => true)); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/posts/2'); $header = $route->response->header(); $this->assertEquals(Router::url('/test', true), $header['Location']); $route = new RedirectRoute('/my_controllers/:action/*', array('controller' => 'tags', 'action' => 'add'), array('persist' => true)); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/my_controllers/do_something/passme/named:param'); $header = $route->response->header(); $this->assertEquals(Router::url('/tags/add/passme/named:param', true), $header['Location']); $route = new RedirectRoute('/my_controllers/:action/*', array('controller' => 'tags', 'action' => 'add')); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/my_controllers/do_something/passme/named:param'); $header = $route->response->header(); $this->assertEquals(Router::url('/tags/add', true), $header['Location']); $route = new RedirectRoute('/:lang/my_controllers', array('controller' => 'tags', 'action' => 'add'), array('lang' => '(nl|en)', 'persist' => array('lang'))); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/nl/my_controllers/'); $header = $route->response->header(); $this->assertEquals(Router::url('/tags/add/lang:nl', true), $header['Location']); Router::$routes = array(); // reset default routes Router::connect('/:lang/preferred_controllers', array('controller' => 'tags', 'action' => 'add'), array('lang' => '(nl|en)', 'persist' => array('lang'))); $route = new RedirectRoute('/:lang/my_controllers', array('controller' => 'tags', 'action' => 'add'), array('lang' => '(nl|en)', 'persist' => array('lang'))); $route->stop = false; $route->response = $this->getMock('CakeResponse', array('_sendHeader')); $route->parse('/nl/my_controllers/'); $header = $route->response->header(); $this->assertEquals(Router::url('/nl/preferred_controllers', true), $header['Location']); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/Route/PluginShortRouteTest.php0000644000000000000000000000412113365153155026500 0ustar rootroot null, 'prefixes' => array())); Router::reload(); } /** * test the parsing of routes. * * @return void */ public function testParsing() { $route = new PluginShortRoute('/:plugin', array('action' => 'index'), array('plugin' => 'foo|bar')); $result = $route->parse('/foo'); $this->assertEquals('foo', $result['plugin']); $this->assertEquals('foo', $result['controller']); $this->assertEquals('index', $result['action']); $result = $route->parse('/wrong'); $this->assertFalse($result, 'Wrong plugin name matched %s'); } /** * test the reverse routing of the plugin shortcut URLs. * * @return void */ public function testMatch() { $route = new PluginShortRoute('/:plugin', array('action' => 'index'), array('plugin' => 'foo|bar')); $result = $route->match(array('plugin' => 'foo', 'controller' => 'posts', 'action' => 'index')); $this->assertFalse($result, 'plugin controller mismatch was converted. %s'); $result = $route->match(array('plugin' => 'foo', 'controller' => 'foo', 'action' => 'index')); $this->assertEquals('/foo', $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/DispatcherTest.php0000644000000000000000000014072413365153155024205 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Routing * @since CakePHP(tm) v 1.2.0.4206 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Dispatcher', 'Routing'); App::uses('DispatcherFilter', 'Routing'); if (!class_exists('AppController', false)) { require_once CAKE . 'Test' . DS . 'test_app' . DS . 'Controller' . DS . 'AppController.php'; } elseif (!defined('APP_CONTROLLER_EXISTS')) { define('APP_CONTROLLER_EXISTS', true); } /** * A testing stub that doesn't send headers. * * @package Cake.Test.Case.Routing */ class DispatcherMockCakeResponse extends CakeResponse { protected function _sendHeader($name, $value = null) { return $name . ' ' . $value; } } /** * TestDispatcher class * * @package Cake.Test.Case.Routing */ class TestDispatcher extends Dispatcher { /** * Controller instance, made publicly available for testing * * @var Controller */ public $controller; /** * invoke method * * @param Controller $controller * @param CakeRequest $request * @return CakeResponse */ protected function _invoke(Controller $controller, CakeRequest $request) { $this->controller = $controller; return parent::_invoke($controller, $request); } /** * Helper function to test single method attaching for dispatcher filters * * @param CakeEvent $event * @return void */ public function filterTest($event) { $event->data['request']->params['eventName'] = $event->name(); } /** * Helper function to test single method attaching for dispatcher filters * * @param CakeEvent * @return void */ public function filterTest2($event) { $event->stopPropagation(); return $event->data['response']; } } /** * MyPluginAppController class * * @package Cake.Test.Case.Routing */ class MyPluginAppController extends AppController { } /** * Abstract Class DispatcherTestAbstractController */ abstract class DispatcherTestAbstractController extends Controller { abstract public function index(); } /** * Interface DispatcherTestInterfaceController */ interface DispatcherTestInterfaceController { public function index(); } /** * MyPluginController class * * @package Cake.Test.Case.Routing */ class MyPluginController extends MyPluginAppController { /** * uses property * * @var array */ public $uses = array(); /** * index method * * @return void */ public function index() { return true; } /** * add method * * @return void */ public function add() { return true; } /** * admin_add method * * @param mixed $id * @return void */ public function admin_add($id = null) { return $id; } } /** * SomePagesController class * * @package Cake.Test.Case.Routing */ class SomePagesController extends AppController { /** * uses property * * @var array */ public $uses = array(); /** * display method * * @param string $page * @return void */ public function display($page = null) { return $page; } /** * index method * * @return void */ public function index() { return true; } /** * Test method for returning responses. * * @return CakeResponse */ public function responseGenerator() { return new CakeResponse(array('body' => 'new response')); } /** * Test file sending * * @return CakeResponse */ public function sendfile() { $this->response->file(CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS . 'css' . DS . 'test_asset.css'); return $this->response; } } /** * OtherPagesController class * * @package Cake.Test.Case.Routing */ class OtherPagesController extends MyPluginAppController { /** * uses property * * @var array */ public $uses = array(); /** * display method * * @param string $page * @return void */ public function display($page = null) { return $page; } /** * index method * * @return void */ public function index() { return true; } } /** * TestDispatchPagesController class * * @package Cake.Test.Case.Routing */ class TestDispatchPagesController extends AppController { /** * uses property * * @var array */ public $uses = array(); /** * admin_index method * * @return void */ public function admin_index() { return true; } /** * camelCased method * * @return void */ public function camelCased() { return true; } } /** * ArticlesTestAppController class * * @package Cake.Test.Case.Routing */ class ArticlesTestAppController extends AppController { } /** * ArticlesTestController class * * @package Cake.Test.Case.Routing */ class ArticlesTestController extends ArticlesTestAppController { /** * uses property * * @var array */ public $uses = array(); /** * admin_index method * * @return void */ public function admin_index() { return true; } /** * fake index method. * * @return void */ public function index() { return true; } } /** * SomePostsController class * * @package Cake.Test.Case.Routing */ class SomePostsController extends AppController { /** * uses property * * @var array */ public $uses = array(); /** * autoRender property * * @var bool */ public $autoRender = false; /** * beforeFilter method * * @return void */ public function beforeFilter() { if ($this->params['action'] === 'index') { $this->params['action'] = 'view'; } else { $this->params['action'] = 'change'; } $this->params['pass'] = array('changed'); } /** * index method * * @return void */ public function index() { return true; } /** * change method * * @return void */ public function change() { return true; } } /** * TestCachedPagesController class * * @package Cake.Test.Case.Routing */ class TestCachedPagesController extends Controller { /** * uses property * * @var array */ public $uses = array(); /** * helpers property * * @var array */ public $helpers = array('Cache', 'Html'); /** * cacheAction property * * @var array */ public $cacheAction = array( 'index' => '+2 sec', 'test_nocache_tags' => '+2 sec', 'view' => '+2 sec' ); /** * Mock out the response object so it doesn't send headers. * * @var string */ protected $_responseClass = 'DispatcherMockCakeResponse'; /** * viewPath property * * @var string */ public $viewPath = 'Posts'; /** * index method * * @return void */ public function index() { $this->render(); } /** * test_nocache_tags method * * @return void */ public function test_nocache_tags() { $this->render(); } /** * view method * * @return void */ public function view($id = null) { $this->render('index'); } /** * test cached forms / tests view object being registered * * @return void */ public function cache_form() { $this->cacheAction = 10; $this->helpers[] = 'Form'; } /** * Test cached views with themes. * * @return void */ public function themed() { $this->cacheAction = 10; $this->viewClass = 'Theme'; $this->theme = 'TestTheme'; } } /** * TimesheetsController class * * @package Cake.Test.Case.Routing */ class TimesheetsController extends Controller { /** * uses property * * @var array */ public $uses = array(); /** * index method * * @return void */ public function index() { return true; } } /** * TestFilterDispatcher class * * @package Cake.Test.Case.Routing */ class TestFilterDispatcher extends DispatcherFilter { public $priority = 10; /** * TestFilterDispatcher::beforeDispatch() * * @param mixed $event * @return CakeResponse|bool */ public function beforeDispatch(CakeEvent $event) { $event->stopPropagation(); $response = $event->data['request']; $response->addParams(array('settings' => $this->settings)); return null; } /** * TestFilterDispatcher::afterDispatch() * * @param mixed $event * @return mixed boolean to stop the event dispatching or null to continue */ public function afterDispatch(CakeEvent $event) { } } /** * DispatcherTest class * * @package Cake.Test.Case.Routing */ class DispatcherTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->_get = $_GET; $_GET = array(); $this->_post = $_POST; $this->_files = $_FILES; $this->_server = $_SERVER; $this->_app = Configure::read('App'); Configure::write('App.base', false); Configure::write('App.baseUrl', false); Configure::write('App.dir', 'app'); Configure::write('App.webroot', 'webroot'); $this->_cache = Configure::read('Cache'); Configure::write('Cache.disable', true); $this->_debug = Configure::read('debug'); App::build(); App::objects('plugin', null, false); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); $_GET = $this->_get; $_POST = $this->_post; $_FILES = $this->_files; $_SERVER = $this->_server; App::build(); CakePlugin::unload(); Configure::write('App', $this->_app); Configure::write('Cache', $this->_cache); Configure::write('debug', $this->_debug); Configure::write('Dispatcher.filters', array()); } /** * testParseParamsWithoutZerosAndEmptyPost method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $request) */ public function testParseParamsWithoutZerosAndEmptyPost() { $Dispatcher = new Dispatcher(); $request = new CakeRequest("/testcontroller/testaction/params1/params2/params3"); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $request)); $Dispatcher->parseParams($event); $this->assertSame($request['controller'], 'testcontroller'); $this->assertSame($request['action'], 'testaction'); $this->assertSame($request['pass'][0], 'params1'); $this->assertSame($request['pass'][1], 'params2'); $this->assertSame($request['pass'][2], 'params3'); $this->assertFalse(!empty($request['form'])); } /** * testParseParamsReturnsPostedData method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $request) */ public function testParseParamsReturnsPostedData() { $_POST['testdata'] = "My Posted Content"; $Dispatcher = new Dispatcher(); $request = new CakeRequest("/"); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $request)); $Dispatcher->parseParams($event); $Dispatcher->parseParams($event); $this->assertEquals("My Posted Content", $request['data']['testdata']); } /** * testParseParamsWithSingleZero method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $test) */ public function testParseParamsWithSingleZero() { $Dispatcher = new Dispatcher(); $test = new CakeRequest("/testcontroller/testaction/1/0/23"); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $test)); $Dispatcher->parseParams($event); $this->assertSame($test['controller'], 'testcontroller'); $this->assertSame($test['action'], 'testaction'); $this->assertSame($test['pass'][0], '1'); $this->assertRegExp('/\\A(?:0)\\z/', $test['pass'][1]); $this->assertSame($test['pass'][2], '23'); } /** * testParseParamsWithManySingleZeros method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $test) */ public function testParseParamsWithManySingleZeros() { $Dispatcher = new Dispatcher(); $test = new CakeRequest("/testcontroller/testaction/0/0/0/0/0/0"); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $test)); $Dispatcher->parseParams($event); $this->assertRegExp('/\\A(?:0)\\z/', $test['pass'][0]); $this->assertRegExp('/\\A(?:0)\\z/', $test['pass'][1]); $this->assertRegExp('/\\A(?:0)\\z/', $test['pass'][2]); $this->assertRegExp('/\\A(?:0)\\z/', $test['pass'][3]); $this->assertRegExp('/\\A(?:0)\\z/', $test['pass'][4]); $this->assertRegExp('/\\A(?:0)\\z/', $test['pass'][5]); } /** * testParseParamsWithManyZerosInEachSectionOfUrl method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $test) */ public function testParseParamsWithManyZerosInEachSectionOfUrl() { $Dispatcher = new Dispatcher(); $test = new CakeRequest("/testcontroller/testaction/000/0000/00000/000000/000000/0000000"); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $test)); $Dispatcher->parseParams($event); $this->assertRegExp('/\\A(?:000)\\z/', $test['pass'][0]); $this->assertRegExp('/\\A(?:0000)\\z/', $test['pass'][1]); $this->assertRegExp('/\\A(?:00000)\\z/', $test['pass'][2]); $this->assertRegExp('/\\A(?:000000)\\z/', $test['pass'][3]); $this->assertRegExp('/\\A(?:000000)\\z/', $test['pass'][4]); $this->assertRegExp('/\\A(?:0000000)\\z/', $test['pass'][5]); } /** * testParseParamsWithMixedOneToManyZerosInEachSectionOfUrl method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $test) */ public function testParseParamsWithMixedOneToManyZerosInEachSectionOfUrl() { $Dispatcher = new Dispatcher(); $test = new CakeRequest("/testcontroller/testaction/01/0403/04010/000002/000030/0000400"); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $test)); $Dispatcher->parseParams($event); $this->assertRegExp('/\\A(?:01)\\z/', $test['pass'][0]); $this->assertRegExp('/\\A(?:0403)\\z/', $test['pass'][1]); $this->assertRegExp('/\\A(?:04010)\\z/', $test['pass'][2]); $this->assertRegExp('/\\A(?:000002)\\z/', $test['pass'][3]); $this->assertRegExp('/\\A(?:000030)\\z/', $test['pass'][4]); $this->assertRegExp('/\\A(?:0000400)\\z/', $test['pass'][5]); } /** * testQueryStringOnRoot method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $request) * @triggers DispatcherTest $Dispatcher, array('request' => $request) */ public function testQueryStringOnRoot() { Router::reload(); Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); Router::connect('/:controller/:action/*'); $_GET = array('coffee' => 'life', 'sleep' => 'sissies'); $Dispatcher = new Dispatcher(); $request = new CakeRequest('posts/home/?coffee=life&sleep=sissies'); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $request)); $Dispatcher->parseParams($event); $this->assertRegExp('/posts/', $request['controller']); $this->assertRegExp('/home/', $request['action']); $this->assertTrue(isset($request['url']['sleep'])); $this->assertTrue(isset($request['url']['coffee'])); $Dispatcher = new Dispatcher(); $request = new CakeRequest('/?coffee=life&sleep=sissy'); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $request)); $Dispatcher->parseParams($event); $this->assertRegExp('/pages/', $request['controller']); $this->assertRegExp('/display/', $request['action']); $this->assertTrue(isset($request['url']['sleep'])); $this->assertTrue(isset($request['url']['coffee'])); $this->assertEquals('life', $request['url']['coffee']); } /** * testMissingController method * * @expectedException MissingControllerException * @expectedExceptionMessage Controller class SomeControllerController could not be found. * @return void */ public function testMissingController() { Router::connect('/:controller/:action/*'); $Dispatcher = new TestDispatcher(); Configure::write('App.baseUrl', '/index.php'); $url = new CakeRequest('some_controller/home/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); } /** * testMissingControllerInterface method * * @expectedException MissingControllerException * @expectedExceptionMessage Controller class DispatcherTestInterfaceController could not be found. * @return void */ public function testMissingControllerInterface() { Router::connect('/:controller/:action/*'); $Dispatcher = new TestDispatcher(); Configure::write('App.baseUrl', '/index.php'); $url = new CakeRequest('dispatcher_test_interface/index'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); } /** * testMissingControllerInterface method * * @expectedException MissingControllerException * @expectedExceptionMessage Controller class DispatcherTestAbstractController could not be found. * @return void */ public function testMissingControllerAbstract() { Router::connect('/:controller/:action/*'); $Dispatcher = new TestDispatcher(); Configure::write('App.baseUrl', '/index.php'); $url = new CakeRequest('dispatcher_test_abstract/index'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); } /** * testDispatch method * * @return void */ public function testDispatchBasic() { App::build(array( 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) )); $Dispatcher = new TestDispatcher(); Configure::write('App.baseUrl', '/index.php'); $url = new CakeRequest('pages/home/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $expected = 'Pages'; $this->assertEquals($expected, $Dispatcher->controller->name); $expected = array('0' => 'home', 'param' => 'value', 'param2' => 'value2'); $this->assertSame($expected, $Dispatcher->controller->passedArgs); Configure::write('App.baseUrl', '/pages/index.php'); $url = new CakeRequest('pages/home'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $expected = 'Pages'; $this->assertEquals($expected, $Dispatcher->controller->name); $url = new CakeRequest('pages/home/'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertNull($Dispatcher->controller->plugin); $expected = 'Pages'; $this->assertEquals($expected, $Dispatcher->controller->name); unset($Dispatcher); require CAKE . 'Config' . DS . 'routes.php'; $Dispatcher = new TestDispatcher(); Configure::write('App.baseUrl', '/timesheets/index.php'); $url = new CakeRequest('timesheets'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $expected = 'Timesheets'; $this->assertEquals($expected, $Dispatcher->controller->name); $url = new CakeRequest('timesheets/'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('Timesheets', $Dispatcher->controller->name); $this->assertEquals('/timesheets/index.php', $url->base); $url = new CakeRequest('test_dispatch_pages/camelCased'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('TestDispatchPages', $Dispatcher->controller->name); $url = new CakeRequest('test_dispatch_pages/camelCased/something. .'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('something. .', $Dispatcher->controller->params['pass'][0], 'Period was chopped off. %s'); } /** * Test that Dispatcher handles actions that return response objects. * * @return void */ public function testDispatchActionReturnsResponse() { Router::connect('/:controller/:action'); $Dispatcher = new Dispatcher(); $request = new CakeRequest('some_pages/responseGenerator'); $response = $this->getMock('CakeResponse', array('_sendHeader')); ob_start(); $Dispatcher->dispatch($request, $response); $result = ob_get_clean(); $this->assertEquals('new response', $result); } /** * testDispatchActionSendsFile * * @return void */ public function testDispatchActionSendsFile() { Router::connect('/:controller/:action'); $Dispatcher = new Dispatcher(); $request = new CakeRequest('some_pages/sendfile'); $response = $this->getMock('CakeResponse', array( 'header', 'type', 'download', '_sendHeader', '_setContentType', '_isActive', '_clearBuffer', '_flushBuffer' )); $response->expects($this->exactly(1)) ->method('_isActive') ->will($this->returnValue(true)); ob_start(); $Dispatcher->dispatch($request, $response); $result = ob_get_clean(); $this->assertEquals("/* this is the test asset css file */\n", $result); } /** * testAdminDispatch method * * @return void */ public function testAdminDispatch() { $_POST = array(); $Dispatcher = new TestDispatcher(); Configure::write('Routing.prefixes', array('admin')); Configure::write('App.baseUrl', '/cake/repo/branches/1.2.x.x/index.php'); $url = new CakeRequest('admin/test_dispatch_pages/index/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); Router::reload(); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('TestDispatchPages', $Dispatcher->controller->name); $this->assertSame($Dispatcher->controller->passedArgs, array('param' => 'value', 'param2' => 'value2')); $this->assertTrue($Dispatcher->controller->params['admin']); $expected = '/cake/repo/branches/1.2.x.x/index.php/admin/test_dispatch_pages/index/param:value/param2:value2'; $this->assertSame($expected, $Dispatcher->controller->here); $expected = '/cake/repo/branches/1.2.x.x/index.php'; $this->assertSame($expected, $Dispatcher->controller->base); } /** * testPluginDispatch method * * @return void * @triggers DispatcherTest $Dispatcher, array('request' => $url) */ public function testPluginDispatch() { $_POST = array(); Router::reload(); $Dispatcher = new TestDispatcher(); Router::connect( '/my_plugin/:controller/*', array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'display') ); $url = new CakeRequest('my_plugin/some_pages/home/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $event = new CakeEvent('DispatcherTest', $Dispatcher, array('request' => $url)); $Dispatcher->parseParams($event); $expected = array( 'pass' => array('home'), 'named' => array('param' => 'value', 'param2' => 'value2'), 'plugin' => 'my_plugin', 'controller' => 'some_pages', 'action' => 'display' ); foreach ($expected as $key => $value) { $this->assertEquals($value, $url[$key], 'Value mismatch ' . $key . ' %'); } $this->assertSame($Dispatcher->controller->plugin, 'MyPlugin'); $this->assertSame($Dispatcher->controller->name, 'SomePages'); $this->assertSame($Dispatcher->controller->params['controller'], 'some_pages'); $this->assertSame($Dispatcher->controller->passedArgs, array('0' => 'home', 'param' => 'value', 'param2' => 'value2')); } /** * testAutomaticPluginDispatch method * * @return void */ public function testAutomaticPluginDispatch() { $_POST = array(); $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; Router::reload(); $Dispatcher = new TestDispatcher(); Router::connect( '/my_plugin/:controller/:action/*', array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'display') ); $Dispatcher->base = false; $url = new CakeRequest('my_plugin/other_pages/index/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertSame($Dispatcher->controller->plugin, 'MyPlugin'); $this->assertSame($Dispatcher->controller->name, 'OtherPages'); $this->assertSame($Dispatcher->controller->action, 'index'); $this->assertSame($Dispatcher->controller->passedArgs, array('param' => 'value', 'param2' => 'value2')); $expected = '/cake/repo/branches/1.2.x.x/my_plugin/other_pages/index/param:value/param2:value2'; $this->assertSame($expected, $url->here); $expected = '/cake/repo/branches/1.2.x.x'; $this->assertSame($expected, $url->base); } /** * testAutomaticPluginControllerDispatch method * * @return void */ public function testAutomaticPluginControllerDispatch() { $plugins = App::objects('plugin'); $plugins[] = 'MyPlugin'; $plugins[] = 'ArticlesTest'; CakePlugin::load('MyPlugin', array('path' => '/fake/path')); Router::reload(); $Dispatcher = new TestDispatcher(); $Dispatcher->base = false; $url = new CakeRequest('my_plugin/my_plugin/add/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertSame($Dispatcher->controller->plugin, 'MyPlugin'); $this->assertSame($Dispatcher->controller->name, 'MyPlugin'); $this->assertSame($Dispatcher->controller->action, 'add'); $this->assertEquals(array('param' => 'value', 'param2' => 'value2'), $Dispatcher->controller->params['named']); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $Dispatcher = new TestDispatcher(); $Dispatcher->base = false; // Simulates the Route for a real plugin, installed in APP/plugins Router::connect('/my_plugin/:controller/:action/*', array('plugin' => 'my_plugin')); $plugin = 'MyPlugin'; $pluginUrl = Inflector::underscore($plugin); $url = new CakeRequest($pluginUrl); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertSame($Dispatcher->controller->plugin, 'MyPlugin'); $this->assertSame($Dispatcher->controller->name, 'MyPlugin'); $this->assertSame($Dispatcher->controller->action, 'index'); $expected = $pluginUrl; $this->assertEquals($expected, $Dispatcher->controller->params['controller']); Configure::write('Routing.prefixes', array('admin')); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $Dispatcher = new TestDispatcher(); $url = new CakeRequest('admin/my_plugin/my_plugin/add/5/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('my_plugin', $Dispatcher->controller->params['plugin']); $this->assertEquals('my_plugin', $Dispatcher->controller->params['controller']); $this->assertEquals('admin_add', $Dispatcher->controller->params['action']); $this->assertEquals(array(5), $Dispatcher->controller->params['pass']); $this->assertEquals(array('param' => 'value', 'param2' => 'value2'), $Dispatcher->controller->params['named']); $this->assertSame($Dispatcher->controller->plugin, 'MyPlugin'); $this->assertSame($Dispatcher->controller->name, 'MyPlugin'); $this->assertSame($Dispatcher->controller->action, 'admin_add'); $expected = array(0 => 5, 'param' => 'value', 'param2' => 'value2'); $this->assertEquals($expected, $Dispatcher->controller->passedArgs); Configure::write('Routing.prefixes', array('admin')); CakePlugin::load('ArticlesTest', array('path' => '/fake/path')); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $Dispatcher = new TestDispatcher(); $Dispatcher->dispatch(new CakeRequest('admin/articles_test'), $response, array('return' => 1)); $this->assertSame($Dispatcher->controller->plugin, 'ArticlesTest'); $this->assertSame($Dispatcher->controller->name, 'ArticlesTest'); $this->assertSame($Dispatcher->controller->action, 'admin_index'); $expected = array( 'pass' => array(), 'named' => array(), 'controller' => 'articles_test', 'plugin' => 'articles_test', 'action' => 'admin_index', 'prefix' => 'admin', 'admin' => true, 'return' => 1 ); foreach ($expected as $key => $value) { $this->assertEquals($expected[$key], $Dispatcher->controller->request[$key], 'Value mismatch ' . $key); } } /** * test Plugin dispatching without controller name and using * plugin short form instead. * * @return void */ public function testAutomaticPluginDispatchWithShortAccess() { CakePlugin::load('MyPlugin', array('path' => '/fake/path')); Router::reload(); $Dispatcher = new TestDispatcher(); $Dispatcher->base = false; $url = new CakeRequest('my_plugin/'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('my_plugin', $Dispatcher->controller->params['controller']); $this->assertEquals('my_plugin', $Dispatcher->controller->params['plugin']); $this->assertEquals('index', $Dispatcher->controller->params['action']); $this->assertFalse(isset($Dispatcher->controller->params['pass'][0])); } /** * test plugin shortcut URLs with controllers that need to be loaded, * the above test uses a controller that has already been included. * * @return void */ public function testPluginShortCutUrlsWithControllerThatNeedsToBeLoaded() { $loaded = class_exists('TestPluginController', false); $this->skipIf($loaded, 'TestPluginController already loaded.'); Router::reload(); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $Dispatcher = new TestDispatcher(); $Dispatcher->base = false; $url = new CakeRequest('test_plugin/'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('test_plugin', $Dispatcher->controller->params['controller']); $this->assertEquals('test_plugin', $Dispatcher->controller->params['plugin']); $this->assertEquals('index', $Dispatcher->controller->params['action']); $this->assertFalse(isset($Dispatcher->controller->params['pass'][0])); $url = new CakeRequest('/test_plugin/tests/index'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('tests', $Dispatcher->controller->params['controller']); $this->assertEquals('test_plugin', $Dispatcher->controller->params['plugin']); $this->assertEquals('index', $Dispatcher->controller->params['action']); $this->assertFalse(isset($Dispatcher->controller->params['pass'][0])); $url = new CakeRequest('/test_plugin/tests/index/some_param'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertEquals('tests', $Dispatcher->controller->params['controller']); $this->assertEquals('test_plugin', $Dispatcher->controller->params['plugin']); $this->assertEquals('index', $Dispatcher->controller->params['action']); $this->assertEquals('some_param', $Dispatcher->controller->params['pass'][0]); App::build(); } /** * testAutomaticPluginControllerMissingActionDispatch method * * @expectedException MissingActionException * @expectedExceptionMessage Action MyPluginController::not_here() could not be found. * @return void */ public function testAutomaticPluginControllerMissingActionDispatch() { Router::reload(); $Dispatcher = new TestDispatcher(); $url = new CakeRequest('my_plugin/not_here/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); } /** * testAutomaticPluginControllerMissingActionDispatch method * * @expectedException MissingActionException * @expectedExceptionMessage Action MyPluginController::param:value() could not be found. * @return void */ public function testAutomaticPluginControllerIndexMissingAction() { Router::reload(); $Dispatcher = new TestDispatcher(); $url = new CakeRequest('my_plugin/param:value/param2:value2'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); } /** * Test dispatching into the TestPlugin in the test_app * * @return void */ public function testTestPluginDispatch() { $Dispatcher = new TestDispatcher(); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); Router::reload(); Router::parse('/'); $url = new CakeRequest('/test_plugin/tests/index'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->assertTrue(class_exists('TestsController')); $this->assertTrue(class_exists('TestPluginAppController')); $this->assertTrue(class_exists('PluginsComponent')); $this->assertEquals('tests', $Dispatcher->controller->params['controller']); $this->assertEquals('test_plugin', $Dispatcher->controller->params['plugin']); $this->assertEquals('index', $Dispatcher->controller->params['action']); App::build(); } /** * Tests that it is possible to attach filter classes to the dispatch cycle * * @return void */ public function testDispatcherFilterSubscriber() { App::build(array( 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load('TestPlugin'); Configure::write('Dispatcher.filters', array( array('callable' => 'TestPlugin.TestDispatcherFilter') )); $dispatcher = new TestDispatcher(); $request = new CakeRequest('/'); $request->params['altered'] = false; $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); $this->assertTrue($request->params['altered']); $this->assertEquals(304, $response->statusCode()); Configure::write('Dispatcher.filters', array( 'TestPlugin.Test2DispatcherFilter', 'TestPlugin.TestDispatcherFilter' )); $dispatcher = new TestDispatcher(); $request = new CakeRequest('/'); $request->params['altered'] = false; $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); $this->assertFalse($request->params['altered']); $this->assertEquals(500, $response->statusCode()); $this->assertNull($dispatcher->controller); } /** * Tests that it is possible to attach filter with config classes to the dispatch cycle * * @return void */ public function testDispatcherFilterSettings() { Configure::write('Dispatcher.filters', array( 'TestFilterDispatcher' => array('service' => 'google.com') )); $Dispatcher = new Dispatcher(); $url = new CakeRequest('some_pages/index'); $response = $this->getMock('CakeResponse'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $settings = $url->param('settings'); $this->assertEquals($settings, array('service' => 'google.com')); } /** * Tests that attaching an inexistent class as filter will throw an exception * * @expectedException MissingDispatcherFilterException * @return void */ public function testDispatcherFilterSuscriberMissing() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load('TestPlugin'); Configure::write('Dispatcher.filters', array( array('callable' => 'TestPlugin.NotAFilter') )); $dispatcher = new TestDispatcher(); $request = new CakeRequest('/'); $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); } /** * Tests it is possible to attach single callables as filters * * @return void */ public function testDispatcherFilterCallable() { App::build(array( 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) ), App::RESET); $dispatcher = new TestDispatcher(); Configure::write('Dispatcher.filters', array( array('callable' => array($dispatcher, 'filterTest'), 'on' => 'before') )); $request = new CakeRequest('/'); $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); $this->assertEquals('Dispatcher.beforeDispatch', $request->params['eventName']); $dispatcher = new TestDispatcher(); Configure::write('Dispatcher.filters', array( array('callable' => array($dispatcher, 'filterTest'), 'on' => 'after') )); $request = new CakeRequest('/'); $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); $this->assertEquals('Dispatcher.afterDispatch', $request->params['eventName']); $dispatcher = new TestDispatcher(); Configure::write('Dispatcher.filters', array( 'filterTest' => array('callable' => array($dispatcher, 'filterTest'), 'on' => 'before') )); $request = new CakeRequest('/'); $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); $this->assertEquals('Dispatcher.beforeDispatch', $request->params['eventName']); // Test that it is possible to skip the route connection process $dispatcher = new TestDispatcher(); Configure::write('Dispatcher.filters', array( array('callable' => array($dispatcher, 'filterTest2'), 'on' => 'before', 'priority' => 1) )); $request = new CakeRequest('/'); $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); $this->assertEmpty($dispatcher->controller); $expected = array('controller' => null, 'action' => null, 'plugin' => null, 'named' => array(), 'pass' => array()); $this->assertEquals($expected, $request->params); $dispatcher = new TestDispatcher(); Configure::write('Dispatcher.filters', array( array('callable' => array($dispatcher, 'filterTest2'), 'on' => 'before', 'priority' => 1) )); $request = new CakeRequest('/'); $request->params['return'] = true; $response = $this->getMock('CakeResponse', array('send')); $response->body('this is a body'); $result = $dispatcher->dispatch($request, $response); $this->assertEquals('this is a body', $result); $request = new CakeRequest('/'); $response = $this->getMock('CakeResponse', array('send')); $response->expects($this->once())->method('send'); $response->body('this is a body'); $result = $dispatcher->dispatch($request, $response); $this->assertNull($result); } /** * testChangingParamsFromBeforeFilter method * * @return void */ public function testChangingParamsFromBeforeFilter() { $Dispatcher = new TestDispatcher(); $response = $this->getMock('CakeResponse'); $url = new CakeRequest('some_posts/index/param:value/param2:value2'); try { $Dispatcher->dispatch($url, $response, array('return' => 1)); $this->fail('No exception.'); } catch (MissingActionException $e) { $this->assertEquals('Action SomePostsController::view() could not be found.', $e->getMessage()); } $url = new CakeRequest('some_posts/something_else/param:value/param2:value2'); $Dispatcher->dispatch($url, $response, array('return' => 1)); $expected = 'SomePosts'; $this->assertEquals($expected, $Dispatcher->controller->name); $expected = 'change'; $this->assertEquals($expected, $Dispatcher->controller->action); $expected = array('changed'); $this->assertSame($expected, $Dispatcher->controller->params['pass']); } /** * testStaticAssets method * * @return void */ public function testAssets() { Router::reload(); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'Vendor' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) )); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); Configure::write('Dispatcher.filters', array('AssetDispatcher')); $Dispatcher = new TestDispatcher(); $response = $this->getMock('CakeResponse', array('_sendHeader')); try { $Dispatcher->dispatch(new CakeRequest('theme/test_theme/../webroot/css/test_asset.css'), $response); $this->fail('No exception'); } catch (MissingControllerException $e) { $this->assertEquals('Controller class ThemeController could not be found.', $e->getMessage()); } try { $Dispatcher->dispatch(new CakeRequest('theme/test_theme/pdfs'), $response); $this->fail('No exception'); } catch (MissingControllerException $e) { $this->assertEquals('Controller class ThemeController could not be found.', $e->getMessage()); } } /** * Data provider for asset filter * * - theme assets. * - plugin assets. * - plugin assets in sub directories. * - unknown plugin assets. * * @return array */ public static function assetProvider() { return array( array( 'theme/test_theme/flash/theme_test.swf', 'View/Themed/TestTheme/webroot/flash/theme_test.swf' ), array( 'theme/test_theme/pdfs/theme_test.pdf', 'View/Themed/TestTheme/webroot/pdfs/theme_test.pdf' ), array( 'theme/test_theme/img/test.jpg', 'View/Themed/TestTheme/webroot/img/test.jpg' ), array( 'theme/test_theme/css/test_asset.css', 'View/Themed/TestTheme/webroot/css/test_asset.css' ), array( 'theme/test_theme/js/theme.js', 'View/Themed/TestTheme/webroot/js/theme.js' ), array( 'theme/test_theme/js/one/theme_one.js', 'View/Themed/TestTheme/webroot/js/one/theme_one.js' ), array( 'theme/test_theme/space%20image.text', 'View/Themed/TestTheme/webroot/space image.text' ), array( 'test_plugin/root.js', 'Plugin/TestPlugin/webroot/root.js' ), array( 'test_plugin/flash/plugin_test.swf', 'Plugin/TestPlugin/webroot/flash/plugin_test.swf' ), array( 'test_plugin/pdfs/plugin_test.pdf', 'Plugin/TestPlugin/webroot/pdfs/plugin_test.pdf' ), array( 'test_plugin/js/test_plugin/test.js', 'Plugin/TestPlugin/webroot/js/test_plugin/test.js' ), array( 'test_plugin/css/test_plugin_asset.css', 'Plugin/TestPlugin/webroot/css/test_plugin_asset.css' ), array( 'test_plugin/img/cake.icon.gif', 'Plugin/TestPlugin/webroot/img/cake.icon.gif' ), array( 'plugin_js/js/plugin_js.js', 'Plugin/PluginJs/webroot/js/plugin_js.js' ), array( 'plugin_js/js/one/plugin_one.js', 'Plugin/PluginJs/webroot/js/one/plugin_one.js' ), array( 'test_plugin/css/unknown.extension', 'Plugin/TestPlugin/webroot/css/unknown.extension' ), array( 'test_plugin/css/theme_one.htc', 'Plugin/TestPlugin/webroot/css/theme_one.htc' ), ); } /** * Test assets * * @dataProvider assetProvider * @outputBuffering enabled * @return void */ public function testAsset($url, $file) { Router::reload(); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'Vendor' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) )); CakePlugin::load(array('TestPlugin', 'PluginJs')); Configure::write('Dispatcher.filters', array('AssetDispatcher')); $Dispatcher = new TestDispatcher(); $response = $this->getMock('CakeResponse', array('_sendHeader')); $Dispatcher->dispatch(new CakeRequest($url), $response); $result = ob_get_clean(); $path = CAKE . 'Test' . DS . 'test_app' . DS . str_replace('/', DS, $file); $file = file_get_contents($path); $this->assertEquals($file, $result); $expected = filesize($path); $headers = $response->header(); $this->assertEquals($expected, $headers['Content-Length']); } /** * test that missing asset processors trigger a 404 with no response body. * * @return void */ public function testMissingAssetProcessor404() { $response = $this->getMock('CakeResponse', array('send')); $Dispatcher = new TestDispatcher(); Configure::write('Asset.filter', array( 'js' => '', 'css' => null )); Configure::write('Dispatcher.filters', array('AssetDispatcher')); $request = new CakeRequest('ccss/cake.generic.css'); $Dispatcher->dispatch($request, $response); $this->assertEquals('404', $response->statusCode()); } /** * Data provider for cached actions. * * - Test simple views * - Test views with nocache tags * - Test requests with named + passed params. * - Test requests with query string params * - Test themed views. * * @return array */ public static function cacheActionProvider() { return array( array('/'), array('test_cached_pages/index'), array('TestCachedPages/index'), array('test_cached_pages/test_nocache_tags'), array('TestCachedPages/test_nocache_tags'), array('test_cached_pages/view/param/param'), array('test_cached_pages/view/foo:bar/value:goo'), array('test_cached_pages/view?q=cakephp'), array('test_cached_pages/themed'), ); } /** * testFullPageCachingDispatch method * * @dataProvider cacheActionProvider * @return void */ public function testFullPageCachingDispatch($url) { Configure::write('Cache.disable', false); Configure::write('Cache.check', true); Configure::write('debug', 2); Router::reload(); Router::connect('/', array('controller' => 'test_cached_pages', 'action' => 'index')); Router::connect('/:controller/:action/*'); App::build(array( 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS), ), App::RESET); $dispatcher = new TestDispatcher(); $request = new CakeRequest($url); $response = $this->getMock('CakeResponse', array('send')); $dispatcher->dispatch($request, $response); $out = $response->body(); Configure::write('Dispatcher.filters', array('CacheDispatcher')); $request = new CakeRequest($url); $response = $this->getMock('CakeResponse', array('send')); $dispatcher = new TestDispatcher(); $dispatcher->dispatch($request, $response); $cached = $response->body(); $cached = preg_replace('//', '', $cached); $this->assertTextEquals($out, $cached); $filename = $this->_cachePath($request->here()); unlink($filename); } /** * testHttpMethodOverrides method * * @return void * @triggers DispatcherTest $dispatcher, array('request' => $request) * @triggers DispatcherTest $dispatcher, array('request' => $request) * @triggers DispatcherTest $dispatcher, array('request' => $request) * @triggers DispatcherTest $dispatcher, array('request' => $request) * @triggers DispatcherTest $dispatcher, array('request' => $request) */ public function testHttpMethodOverrides() { Router::reload(); Router::mapResources('Posts'); $_SERVER['REQUEST_METHOD'] = 'POST'; $dispatcher = new Dispatcher(); $request = new CakeRequest('/posts'); $event = new CakeEvent('DispatcherTest', $dispatcher, array('request' => $request)); $dispatcher->parseParams($event); $expected = array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'add', '[method]' => 'POST'); foreach ($expected as $key => $value) { $this->assertEquals($value, $request[$key], 'Value mismatch for ' . $key . ' %s'); } $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'PUT'; $request = new CakeRequest('/posts/5'); $event = new CakeEvent('DispatcherTest', $dispatcher, array('request' => $request)); $dispatcher->parseParams($event); $expected = array( 'pass' => array('5'), 'named' => array(), 'id' => '5', 'plugin' => null, 'controller' => 'posts', 'action' => 'edit', '[method]' => 'PUT' ); foreach ($expected as $key => $value) { $this->assertEquals($value, $request[$key], 'Value mismatch for ' . $key . ' %s'); } unset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); $_SERVER['REQUEST_METHOD'] = 'GET'; $request = new CakeRequest('/posts/5'); $event = new CakeEvent('DispatcherTest', $dispatcher, array('request' => $request)); $dispatcher->parseParams($event); $expected = array('pass' => array('5'), 'named' => array(), 'id' => '5', 'plugin' => null, 'controller' => 'posts', 'action' => 'view', '[method]' => 'GET'); foreach ($expected as $key => $value) { $this->assertEquals($value, $request[$key], 'Value mismatch for ' . $key . ' %s'); } $_POST['_method'] = 'PUT'; $request = new CakeRequest('/posts/5'); $event = new CakeEvent('DispatcherTest', $dispatcher, array('request' => $request)); $dispatcher->parseParams($event); $expected = array('pass' => array('5'), 'named' => array(), 'id' => '5', 'plugin' => null, 'controller' => 'posts', 'action' => 'edit', '[method]' => 'PUT'); foreach ($expected as $key => $value) { $this->assertEquals($value, $request[$key], 'Value mismatch for ' . $key . ' %s'); } $_POST['_method'] = 'POST'; $_POST['data'] = array('Post' => array('title' => 'New Post')); $_POST['extra'] = 'data'; $_SERVER = array(); $request = new CakeRequest('/posts'); $event = new CakeEvent('DispatcherTest', $dispatcher, array('request' => $request)); $dispatcher->parseParams($event); $expected = array( 'pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'add', '[method]' => 'POST', 'data' => array('extra' => 'data', 'Post' => array('title' => 'New Post')), ); foreach ($expected as $key => $value) { $this->assertEquals($value, $request[$key], 'Value mismatch for ' . $key . ' %s'); } unset($_POST['_method']); } /** * cachePath method * * @param string $here * @return string */ protected function _cachePath($here) { $path = $here; if ($here === '/') { $path = 'home'; } $path = strtolower(Inflector::slug($path)); $filename = CACHE . 'views' . DS . $path . '.php'; if (!file_exists($filename)) { $filename = CACHE . 'views' . DS . $path . '_index.php'; } return $filename; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/Filter/0000755000000000000000000000000013365153155021763 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/Filter/AssetDispatcherTest.php0000644000000000000000000002257013365153155026430 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests * @package Cake.Test.Case.Routing.Filter * @since CakePHP(tm) v 2.2 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('AssetDispatcher', 'Routing/Filter'); App::uses('CakeEvent', 'Event'); App::uses('CakeResponse', 'Network'); /** * AssetDispatcherTest * * @package Cake.Test.Case.Routing.Filter */ class AssetDispatcherTest extends CakeTestCase { /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Configure::write('Dispatcher.filters', array()); } /** * test that asset filters work for theme and plugin assets * * @return void * @triggers DispatcherTest $this, compact('request', 'response') * @triggers DispatcherTest $this, compact('request', 'response') * @triggers DispatcherTest $this, compact('request', 'response') * @triggers DispatcherTest $this, compact('request', 'response') * @triggers DispatcherTest $this, compact('request', 'response') * @triggers DispatcherTest $this, compact('request', 'response') */ public function testAssetFilterForThemeAndPlugins() { $filter = new AssetDispatcher(); $response = $this->getMock('CakeResponse', array('_sendHeader')); Configure::write('Asset.filter', array( 'js' => '', 'css' => '' )); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) ), App::RESET); $request = new CakeRequest('theme/test_theme/ccss/cake.generic.css'); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertSame($response, $filter->beforeDispatch($event)); $this->assertTrue($event->isStopped()); $request = new CakeRequest('theme/test_theme/cjs/debug_kit.js'); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertSame($response, $filter->beforeDispatch($event)); $this->assertTrue($event->isStopped()); $request = new CakeRequest('test_plugin/ccss/cake.generic.css'); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertSame($response, $filter->beforeDispatch($event)); $this->assertTrue($event->isStopped()); $request = new CakeRequest('test_plugin/cjs/debug_kit.js'); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertSame($response, $filter->beforeDispatch($event)); $this->assertTrue($event->isStopped()); $request = new CakeRequest('css/ccss/debug_kit.css'); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertNull($filter->beforeDispatch($event)); $this->assertFalse($event->isStopped()); $request = new CakeRequest('js/cjs/debug_kit.js'); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertNull($filter->beforeDispatch($event)); $this->assertFalse($event->isStopped()); } /** * AssetDispatcher should not 404 extensions that could be handled * by Routing. * * @return void * @triggers DispatcherTest $this, compact('request', 'response') */ public function testNoHandleRoutedExtension() { $filter = new AssetDispatcher(); $response = $this->getMock('CakeResponse', array('_sendHeader')); Configure::write('Asset.filter', array( 'js' => '', 'css' => '' )); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) ), App::RESET); Router::parseExtensions('json'); Router::connect('/test_plugin/api/v1/:action', array('controller' => 'api')); CakePlugin::load('TestPlugin'); $request = new CakeRequest('test_plugin/api/v1/forwarding.json'); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertNull($filter->beforeDispatch($event)); $this->assertFalse($event->isStopped(), 'Events for routed extensions should not be stopped'); } /** * Tests that $response->checkNotModified() is called and bypasses * file dispatching * * @return void * @triggers DispatcherTest $this, compact('request', 'response') * @triggers DispatcherTest $this, compact('request', 'response') */ public function testNotModified() { $filter = new AssetDispatcher(); Configure::write('Asset.filter', array( 'js' => '', 'css' => '' )); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) )); $time = filemtime(App::themePath('TestTheme') . 'webroot' . DS . 'img' . DS . 'cake.power.gif'); $time = new DateTime('@' . $time); $response = $this->getMock('CakeResponse', array('send', 'checkNotModified')); $request = new CakeRequest('theme/test_theme/img/cake.power.gif'); $response->expects($this->once())->method('checkNotModified') ->with($request) ->will($this->returnValue(true)); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); ob_start(); $this->assertSame($response, $filter->beforeDispatch($event)); ob_end_clean(); $this->assertEquals(200, $response->statusCode()); $this->assertEquals($time->format('D, j M Y H:i:s') . ' GMT', $response->modified()); $response = $this->getMock('CakeResponse', array('_sendHeader', 'checkNotModified')); $request = new CakeRequest('theme/test_theme/img/cake.power.gif'); $response->expects($this->once())->method('checkNotModified') ->with($request) ->will($this->returnValue(true)); $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); $this->assertSame($response, $filter->beforeDispatch($event)); $this->assertEquals($time->format('D, j M Y H:i:s') . ' GMT', $response->modified()); } /** * Test that no exceptions are thrown for //index.php type URLs. * * @return void * @triggers Dispatcher.beforeRequest $this, compact('request', 'response') */ public function test404OnDoubleSlash() { $filter = new AssetDispatcher(); $response = $this->getMock('CakeResponse', array('_sendHeader')); $request = new CakeRequest('//index.php'); $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); $this->assertNull($filter->beforeDispatch($event)); $this->assertFalse($event->isStopped()); } /** * Test that attempts to traverse directories are prevented. * * @return void * @triggers Dispatcher.beforeRequest $this, compact('request', 'response') */ public function test404OnDoubleDot() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) ), App::RESET); $response = $this->getMock('CakeResponse', array('_sendHeader')); $request = new CakeRequest('theme/test_theme/../../../../../../VERSION.txt'); $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); $filter = new AssetDispatcher(); $this->assertNull($filter->beforeDispatch($event)); $this->assertFalse($event->isStopped()); } /** * Test that attempts to traverse directories with urlencoded paths fail. * * @return void * @triggers Dispatcher.beforeRequest $this, compact('request', 'response') */ public function test404OnDoubleDotEncoded() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) ), App::RESET); $response = $this->getMock('CakeResponse', array('_sendHeader', 'send')); $request = new CakeRequest('theme/test_theme/%2e./%2e./%2e./%2e./%2e./%2e./VERSION.txt'); $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); $response->expects($this->never())->method('send'); $filter = new AssetDispatcher(); $this->assertNull($filter->beforeDispatch($event)); $this->assertFalse($event->isStopped()); } /** * Test asset content length is unset * * If content length is unset, then the webserver can figure it out. * * @outputBuffering enabled * @return void */ public function testAssetContentLength() { Router::reload(); Configure::write('Dispatcher.filters', array('AssetDispatcher')); App::build(array( 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) )); $url = 'theme/test_theme/css/test_asset.css'; $file = 'View/Themed/TestTheme/webroot/css/test_asset.css'; $request = new CakeRequest($url); $response = $this->getMock('CakeResponse', array('_sendHeader', 'send')); $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); $filter = new AssetDispatcher(); $filter->beforeDispatch($event); $result = ob_get_clean(); $path = CAKE . 'Test' . DS . 'test_app' . DS . str_replace('/', DS, $file); $file = file_get_contents($path); $this->assertEquals($file, $result); $headers = $response->header(); $this->assertFalse($headers['Content-Length']); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Routing/RouterTest.php0000644000000000000000000031136413365153155023377 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Routing * @since CakePHP(tm) v 1.2.0.4206 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Router', 'Routing'); App::uses('CakeResponse', 'Network'); if (!defined('FULL_BASE_URL')) { define('FULL_BASE_URL', 'https://cakephp.org'); } /** * RouterTest class * * @package Cake.Test.Case.Routing */ class RouterTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); Configure::write('Routing', array('admin' => null, 'prefixes' => array())); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); CakePlugin::unload(); Router::fullBaseUrl(''); Configure::write('App.fullBaseUrl', 'http://localhost'); } /** * testFullBaseUrl method * * @return void */ public function testFullBaseUrl() { $this->assertRegExp('/^http(s)?:\/\//', Router::url('/', true)); $this->assertRegExp('/^http(s)?:\/\//', Router::url(null, true)); $this->assertRegExp('/^http(s)?:\/\//', Router::url(array('full_base' => true))); $this->assertSame(FULL_BASE_URL . '/', Router::url(array('full_base' => true))); } /** * Tests that the base URL can be changed at runtime. * * @return void */ public function testBaseUrl() { $this->assertEquals(FULL_BASE_URL, Router::fullBaseUrl()); Router::fullBaseUrl('http://example.com'); $this->assertEquals('http://example.com/', Router::url('/', true)); $this->assertEquals('http://example.com', Configure::read('App.fullBaseUrl')); Router::fullBaseUrl('https://example.com'); $this->assertEquals('https://example.com/', Router::url('/', true)); $this->assertEquals('https://example.com', Configure::read('App.fullBaseUrl')); } /** * Test that Router uses App.base to build URL's when there are no stored * request objects. * * @return void */ public function testBaseUrlWithBasePath() { Configure::write('App.base', '/cakephp'); Router::fullBaseUrl('http://example.com'); $this->assertEquals('http://example.com/cakephp/tasks', Router::url('/tasks', true)); } /** * testRouteDefaultParams method * * @return void */ public function testRouteDefaultParams() { Router::connect('/:controller', array('controller' => 'posts')); $this->assertEquals(Router::url(array('action' => 'index')), '/'); } /** * testMapResources method * * @return void */ public function testMapResources() { $resources = Router::mapResources('Posts'); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/posts'); $this->assertEquals(array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'index', '[method]' => 'GET'), $result); $this->assertEquals(array('posts'), $resources); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/posts/13'); $this->assertEquals(array('pass' => array('13'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'view', 'id' => '13', '[method]' => 'GET'), $result); $_SERVER['REQUEST_METHOD'] = 'POST'; $result = Router::parse('/posts'); $this->assertEquals(array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'add', '[method]' => 'POST'), $result); $_SERVER['REQUEST_METHOD'] = 'PUT'; $result = Router::parse('/posts/13'); $this->assertEquals(array('pass' => array('13'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'edit', 'id' => '13', '[method]' => 'PUT'), $result); $result = Router::parse('/posts/475acc39-a328-44d3-95fb-015000000000'); $this->assertEquals(array('pass' => array('475acc39-a328-44d3-95fb-015000000000'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'edit', 'id' => '475acc39-a328-44d3-95fb-015000000000', '[method]' => 'PUT'), $result); $_SERVER['REQUEST_METHOD'] = 'DELETE'; $result = Router::parse('/posts/13'); $this->assertEquals(array('pass' => array('13'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'delete', 'id' => '13', '[method]' => 'DELETE'), $result); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/posts/add'); $this->assertSame(array(), $result); Router::reload(); $resources = Router::mapResources('Posts', array('id' => '[a-z0-9_]+')); $this->assertEquals(array('posts'), $resources); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/posts/add'); $this->assertEquals(array('pass' => array('add'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'view', 'id' => 'add', '[method]' => 'GET'), $result); $_SERVER['REQUEST_METHOD'] = 'PUT'; $result = Router::parse('/posts/name'); $this->assertEquals(array('pass' => array('name'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'edit', 'id' => 'name', '[method]' => 'PUT'), $result); } /** * testMapResources with plugin controllers. * * @return void */ public function testPluginMapResources() { App::build(array( 'Plugin' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS ) )); $resources = Router::mapResources('TestPlugin.TestPlugin'); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/test_plugin/test_plugin'); $expected = array( 'pass' => array(), 'named' => array(), 'plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index', '[method]' => 'GET' ); $this->assertEquals($expected, $result); $this->assertEquals(array('test_plugin'), $resources); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/test_plugin/test_plugin/13'); $expected = array( 'pass' => array('13'), 'named' => array(), 'plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'view', 'id' => '13', '[method]' => 'GET' ); $this->assertEquals($expected, $result); } /** * testMapResources with custom connectOptions */ public function testMapResourcesConnectOptions() { App::build(array( 'Plugin' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS ) )); CakePlugin::load('TestPlugin'); App::uses('TestRoute', 'TestPlugin.Routing/Route'); Router::mapResources('Posts', array( 'connectOptions' => array( 'routeClass' => 'TestPlugin.TestRoute', 'foo' => '^(bar)$', ), )); $route = end(Router::$routes); $this->assertInstanceOf('TestRoute', $route); $this->assertEquals('^(bar)$', $route->options['foo']); } /** * Test mapResources with a plugin and prefix. * * @return void */ public function testPluginMapResourcesWithPrefix() { App::build(array( 'Plugin' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS ) )); $resources = Router::mapResources('TestPlugin.TestPlugin', array('prefix' => '/api/')); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/api/test_plugin'); $expected = array( 'pass' => array(), 'named' => array(), 'plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index', '[method]' => 'GET' ); $this->assertEquals($expected, $result); $this->assertEquals(array('test_plugin'), $resources); Router::mapResources('Posts', array('prefix' => 'api')); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/api/posts'); $expected = array( 'pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'index', '[method]' => 'GET' ); $this->assertEquals($expected, $result); } /** * testMultipleResourceRoute method * * @return void */ public function testMultipleResourceRoute() { Router::connect('/:controller', array('action' => 'index', '[method]' => array('GET', 'POST'))); $_SERVER['REQUEST_METHOD'] = 'GET'; $result = Router::parse('/posts'); $this->assertEquals(array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'index', '[method]' => array('GET', 'POST')), $result); $_SERVER['REQUEST_METHOD'] = 'POST'; $result = Router::parse('/posts'); $this->assertEquals(array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'index', '[method]' => array('GET', 'POST')), $result); } /** * testGenerateUrlResourceRoute method * * @return void */ public function testGenerateUrlResourceRoute() { Router::mapResources('Posts'); $result = Router::url(array('controller' => 'posts', 'action' => 'index', '[method]' => 'GET')); $expected = '/posts'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'view', '[method]' => 'GET', 'id' => 10)); $expected = '/posts/10'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'add', '[method]' => 'POST')); $expected = '/posts'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'edit', '[method]' => 'PUT', 'id' => 10)); $expected = '/posts/10'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'delete', '[method]' => 'DELETE', 'id' => 10)); $expected = '/posts/10'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'edit', '[method]' => 'POST', 'id' => 10)); $expected = '/posts/10'; $this->assertEquals($expected, $result); } /** * testUrlNormalization method * * @return void */ public function testUrlNormalization() { $expected = '/users/logout'; $result = Router::normalize('/users/logout/'); $this->assertEquals($expected, $result); $result = Router::normalize('//users//logout//'); $this->assertEquals($expected, $result); $result = Router::normalize('users/logout'); $this->assertEquals($expected, $result); $result = Router::normalize(array('controller' => 'users', 'action' => 'logout')); $this->assertEquals($expected, $result); $result = Router::normalize('/'); $this->assertEquals('/', $result); $result = Router::normalize('http://google.com/'); $this->assertEquals('http://google.com/', $result); $result = Router::normalize('http://google.com//'); $this->assertEquals('http://google.com//', $result); $result = Router::normalize('/users/login/scope://foo'); $this->assertEquals('/users/login/scope:/foo', $result); $result = Router::normalize('/recipe/recipes/add'); $this->assertEquals('/recipe/recipes/add', $result); $request = new CakeRequest(); $request->base = '/us'; Router::setRequestInfo($request); $result = Router::normalize('/us/users/logout/'); $this->assertEquals('/users/logout', $result); Router::reload(); $request = new CakeRequest(); $request->base = '/cake_12'; Router::setRequestInfo($request); $result = Router::normalize('/cake_12/users/logout/'); $this->assertEquals('/users/logout', $result); Router::reload(); $_back = Configure::read('App.baseUrl'); Configure::write('App.baseUrl', '/'); $request = new CakeRequest(); $request->base = '/'; Router::setRequestInfo($request); $result = Router::normalize('users/login'); $this->assertEquals('/users/login', $result); Configure::write('App.baseUrl', $_back); Router::reload(); $request = new CakeRequest(); $request->base = 'beer'; Router::setRequestInfo($request); $result = Router::normalize('beer/admin/beers_tags/add'); $this->assertEquals('/admin/beers_tags/add', $result); $result = Router::normalize('/admin/beers_tags/add'); $this->assertEquals('/admin/beers_tags/add', $result); } /** * test generation of basic URLs. * * @return void */ public function testUrlGenerationBasic() { extract(Router::getNamedExpressions()); $request = new CakeRequest(); $request->addParams(array( 'action' => 'index', 'plugin' => null, 'controller' => 'subscribe', 'admin' => true )); $request->base = '/magazine'; $request->here = '/magazine'; $request->webroot = '/magazine/'; Router::setRequestInfo($request); $result = Router::url(); $this->assertEquals('/magazine', $result); Router::reload(); Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); $out = Router::url(array('controller' => 'pages', 'action' => 'display', 'home')); $this->assertEquals('/', $out); Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); $result = Router::url(array('controller' => 'pages', 'action' => 'display', 'about')); $expected = '/pages/about'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:plugin/:id/*', array('controller' => 'posts', 'action' => 'view'), array('id' => $ID)); Router::parse('/'); $result = Router::url(array('plugin' => 'cake_plugin', 'controller' => 'posts', 'action' => 'view', 'id' => '1')); $expected = '/cake_plugin/1'; $this->assertEquals($expected, $result); $result = Router::url(array('plugin' => 'cake_plugin', 'controller' => 'posts', 'action' => 'view', 'id' => '1', '0')); $expected = '/cake_plugin/1/0'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:controller/:action/:id', array(), array('id' => $ID)); Router::parse('/'); $result = Router::url(array('controller' => 'posts', 'action' => 'view', 'id' => '1')); $expected = '/posts/view/1'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:controller/:id', array('action' => 'view')); Router::parse('/'); $result = Router::url(array('controller' => 'posts', 'action' => 'view', 'id' => '1')); $expected = '/posts/1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0')); $expected = '/posts/index/0'; $this->assertEquals($expected, $result); Router::connect('/view/*', array('controller' => 'posts', 'action' => 'view')); Router::promote(); $result = Router::url(array('controller' => 'posts', 'action' => 'view', '1')); $expected = '/view/1'; $this->assertEquals($expected, $result); Router::reload(); $request = new CakeRequest(); $request->addParams(array( 'action' => 'index', 'plugin' => null, 'controller' => 'real_controller_name' )); $request->base = '/'; $request->here = '/'; $request->webroot = '/'; Router::setRequestInfo($request); Router::connect('short_controller_name/:action/*', array('controller' => 'real_controller_name')); Router::parse('/'); $result = Router::url(array('controller' => 'real_controller_name', 'page' => '1')); $expected = '/short_controller_name/index/page:1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'add')); $expected = '/short_controller_name/add'; $this->assertEquals($expected, $result); Router::reload(); Router::parse('/'); $request = new CakeRequest(); $request->addParams(array( 'action' => 'index', 'plugin' => null, 'controller' => 'users', 'url' => array('url' => 'users') )); $request->base = '/'; $request->here = '/'; $request->webroot = '/'; Router::setRequestInfo($request); $result = Router::url(array('action' => 'login')); $expected = '/users/login'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/page/*', array('plugin' => null, 'controller' => 'pages', 'action' => 'view')); Router::parse('/'); $result = Router::url(array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'view', 'my-page')); $expected = '/my_plugin/pages/view/my-page'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/contact/:action', array('plugin' => 'contact', 'controller' => 'contact')); Router::parse('/'); $result = Router::url(array('plugin' => 'contact', 'controller' => 'contact', 'action' => 'me')); $expected = '/contact/me'; $this->assertEquals($expected, $result); Router::reload(); $request = new CakeRequest(); $request->addParams(array( 'action' => 'index', 'plugin' => 'myplugin', 'controller' => 'mycontroller', 'admin' => false )); $request->base = '/'; $request->here = '/'; $request->webroot = '/'; Router::setRequestInfo($request); $result = Router::url(array('plugin' => null, 'controller' => 'myothercontroller')); $expected = '/myothercontroller'; $this->assertEquals($expected, $result); } /** * Test that catch all routes work with a variety of falsey inputs. * * @return void */ public function testUrlCatchAllRoute() { Router::connect('/*', array('controller' => 'categories', 'action' => 'index')); $result = Router::url(array('controller' => 'categories', 'action' => 'index', '0')); $this->assertEquals('/0', $result); $expected = array( 'plugin' => null, 'controller' => 'categories', 'action' => 'index', 'pass' => array('0'), 'named' => array() ); $result = Router::parse('/0'); $this->assertEquals($expected, $result); $result = Router::parse('0'); $this->assertEquals($expected, $result); } /** * Tests using arrays in named parameters * * @return void */ public function testArrayNamedParameters() { $result = Router::url(array('controller' => 'tests', 'pages' => array( 1, 2, 3 ))); $expected = '/tests/index/pages%5B0%5D:1/pages%5B1%5D:2/pages%5B2%5D:3'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'tests', 'pages' => array( 'param1' => array( 'one', 'two' ), 'three' ) )); $expected = '/tests/index/pages%5Bparam1%5D%5B0%5D:one/pages%5Bparam1%5D%5B1%5D:two/pages%5B0%5D:three'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'tests', 'pages' => array( 'param1' => array( 'one' => 1, 'two' => 2 ), 'three' ) )); $expected = '/tests/index/pages%5Bparam1%5D%5Bone%5D:1/pages%5Bparam1%5D%5Btwo%5D:2/pages%5B0%5D:three'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'tests', 'super' => array( 'nested' => array( 'array' => 'awesome', 'something' => 'else' ), 'cool' ) )); $expected = '/tests/index/super%5Bnested%5D%5Barray%5D:awesome/super%5Bnested%5D%5Bsomething%5D:else/super%5B0%5D:cool'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'tests', 'namedParam' => array( 'keyed' => 'is an array', 'test' ))); $expected = '/tests/index/namedParam%5Bkeyed%5D:is%20an%20array/namedParam%5B0%5D:test'; $this->assertEquals($expected, $result); } /** * Test generation of routes with query string parameters. * * @return void */ public function testUrlGenerationWithQueryStrings() { $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2')); $expected = '/posts/index/0?var=test&var2=test2'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', '0', '?' => 'var=test&var2=test2')); $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', '0', '?' => array('var' => 'test', 'var2' => 'test2'))); $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', '0', '?' => array('var' => null))); $this->assertEquals('/posts/index/0', $result); $result = Router::url(array('controller' => 'posts', '0', '?' => 'var=test&var2=test2', '#' => 'unencoded string %')); $expected = '/posts/index/0?var=test&var2=test2#unencoded string %'; $this->assertEquals($expected, $result); } /** * test that regex validation of keyed route params is working. * * @return void */ public function testUrlGenerationWithRegexQualifiedParams() { Router::connect( ':language/galleries', array('controller' => 'galleries', 'action' => 'index'), array('language' => '[a-z]{3}') ); Router::connect( '/:language/:admin/:controller/:action/*', array('admin' => 'admin'), array('language' => '[a-z]{3}', 'admin' => 'admin') ); Router::connect('/:language/:controller/:action/*', array(), array('language' => '[a-z]{3}') ); $result = Router::url(array('admin' => false, 'language' => 'dan', 'action' => 'index', 'controller' => 'galleries')); $expected = '/dan/galleries'; $this->assertEquals($expected, $result); $result = Router::url(array('admin' => false, 'language' => 'eng', 'action' => 'index', 'controller' => 'galleries')); $expected = '/eng/galleries'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:language/pages', array('controller' => 'pages', 'action' => 'index'), array('language' => '[a-z]{3}') ); Router::connect('/:language/:controller/:action/*', array(), array('language' => '[a-z]{3}')); $result = Router::url(array('language' => 'eng', 'action' => 'index', 'controller' => 'pages')); $expected = '/eng/pages'; $this->assertEquals($expected, $result); $result = Router::url(array('language' => 'eng', 'controller' => 'pages')); $this->assertEquals($expected, $result); $result = Router::url(array('language' => 'eng', 'controller' => 'pages', 'action' => 'add')); $expected = '/eng/pages/add'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/forestillinger/:month/:year/*', array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'), array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}') ); Router::parse('/'); $result = Router::url(array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'month' => 10, 'year' => 2007, 'min-forestilling')); $expected = '/forestillinger/10/2007/min-forestilling'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/kalender/:month/:year/*', array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'), array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}') ); Router::connect('/kalender/*', array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar')); Router::parse('/'); $result = Router::url(array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'min-forestilling')); $expected = '/kalender/min-forestilling'; $this->assertEquals($expected, $result); $result = Router::url(array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'year' => 2007, 'month' => 10, 'min-forestilling')); $expected = '/kalender/10/2007/min-forestilling'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:controller/:action/*', array(), array( 'controller' => 'source|wiki|commits|tickets|comments|view', 'action' => 'branches|history|branch|logs|view|start|add|edit|modify' )); } /** * Test URL generation with an admin prefix * * @return void */ public function testUrlGenerationWithAdminPrefix() { Configure::write('Routing.prefixes', array('admin')); Router::reload(); Router::connectNamed(array('event', 'lang')); Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); Router::connect('/pages/contact_us', array('controller' => 'pages', 'action' => 'contact_us')); Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); Router::connect('/reset/*', array('admin' => true, 'controller' => 'users', 'action' => 'reset')); Router::connect('/tests', array('controller' => 'tests', 'action' => 'index')); Router::parseExtensions('rss'); $request = new CakeRequest(); $request->addParams(array( 'controller' => 'registrations', 'action' => 'admin_index', 'plugin' => null, 'prefix' => 'admin', 'admin' => true, 'ext' => 'html' )); $request->base = ''; $request->here = '/admin/registrations/index'; $request->webroot = '/'; Router::setRequestInfo($request); $result = Router::url(array('page' => 2)); $expected = '/admin/registrations/index/page:2'; $this->assertEquals($expected, $result); Router::reload(); $request = new CakeRequest(); $request->addParams(array( 'controller' => 'subscriptions', 'action' => 'admin_index', 'plugin' => null, 'admin' => true, 'url' => array('url' => 'admin/subscriptions/index/page:2') )); $request->base = '/magazine'; $request->here = '/magazine/admin/subscriptions/index/page:2'; $request->webroot = '/magazine/'; Router::setRequestInfo($request); Router::parse('/'); $result = Router::url(array('page' => 3)); $expected = '/magazine/admin/subscriptions/index/page:3'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/admin/subscriptions/:action/*', array('controller' => 'subscribe', 'admin' => true, 'prefix' => 'admin')); Router::parse('/'); $request = new CakeRequest(); $request->addParams(array( 'action' => 'admin_index', 'plugin' => null, 'controller' => 'subscribe', 'admin' => true, 'url' => array('url' => 'admin/subscriptions/edit/1') )); $request->base = '/magazine'; $request->here = '/magazine/admin/subscriptions/edit/1'; $request->webroot = '/magazine/'; Router::setRequestInfo($request); $result = Router::url(array('action' => 'edit', 1)); $expected = '/magazine/admin/subscriptions/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('admin' => true, 'controller' => 'users', 'action' => 'login')); $expected = '/magazine/admin/users/login'; $this->assertEquals($expected, $result); Router::reload(); $request = new CakeRequest(); $request->addParams(array( 'admin' => true, 'action' => 'index', 'plugin' => null, 'controller' => 'users', 'url' => array('url' => 'users') )); $request->base = '/'; $request->here = '/'; $request->webroot = '/'; Router::setRequestInfo($request); Router::connect('/page/*', array('controller' => 'pages', 'action' => 'view', 'admin' => true, 'prefix' => 'admin')); Router::parse('/'); $result = Router::url(array('admin' => true, 'controller' => 'pages', 'action' => 'view', 'my-page')); $expected = '/page/my-page'; $this->assertEquals($expected, $result); Router::reload(); $request = new CakeRequest(); $request->addParams(array( 'plugin' => null, 'controller' => 'pages', 'action' => 'admin_add', 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/pages/add') )); $request->base = ''; $request->here = '/admin/pages/add'; $request->webroot = '/'; Router::setRequestInfo($request); Router::parse('/'); $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'add', 'id' => false)); $expected = '/admin/pages/add'; $this->assertEquals($expected, $result); Router::reload(); Router::parse('/'); $request = new CakeRequest(); $request->addParams(array( 'plugin' => null, 'controller' => 'pages', 'action' => 'admin_add', 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/pages/add') )); $request->base = ''; $request->here = '/admin/pages/add'; $request->webroot = '/'; Router::setRequestInfo($request); $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'add', 'id' => false)); $expected = '/admin/pages/add'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/admin/:controller/:action/:id', array('admin' => true), array('id' => '[0-9]+')); Router::parse('/'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'pages', 'action' => 'admin_edit', 'pass' => array('284'), 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/pages/edit/284') ))->addPaths(array( 'base' => '', 'here' => '/admin/pages/edit/284', 'webroot' => '/' )) ); $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'edit', 'id' => '284')); $expected = '/admin/pages/edit/284'; $this->assertEquals($expected, $result); Router::reload(); Router::parse('/'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'pages', 'action' => 'admin_add', 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/pages/add') ))->addPaths(array( 'base' => '', 'here' => '/admin/pages/add', 'webroot' => '/' )) ); $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'add', 'id' => false)); $expected = '/admin/pages/add'; $this->assertEquals($expected, $result); Router::reload(); Router::parse('/'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'pages', 'action' => 'admin_edit', 'prefix' => 'admin', 'admin' => true, 'pass' => array('284'), 'url' => array('url' => 'admin/pages/edit/284') ))->addPaths(array( 'base' => '', 'here' => '/admin/pages/edit/284', 'webroot' => '/' )) ); $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'edit', 284)); $expected = '/admin/pages/edit/284'; $this->assertEquals($expected, $result); Router::reload(); Router::connect('/admin/posts/*', array('controller' => 'posts', 'action' => 'index', 'admin' => true)); Router::parse('/'); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'posts', 'action' => 'admin_index', 'prefix' => 'admin', 'admin' => true, 'pass' => array('284'), 'url' => array('url' => 'admin/posts') ))->addPaths(array( 'base' => '', 'here' => '/admin/posts', 'webroot' => '/' )) ); $result = Router::url(array('all')); $expected = '/admin/posts/all'; $this->assertEquals($expected, $result); } /** * testUrlGenerationWithExtensions method * * @return void */ public function testUrlGenerationWithExtensions() { Router::parse('/'); $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'add', 'id' => null, 'ext' => 'json')); $expected = '/articles/add.json'; $this->assertEquals($expected, $result); $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'add', 'ext' => 'json')); $expected = '/articles/add.json'; $this->assertEquals($expected, $result); $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'id' => null, 'ext' => 'json')); $expected = '/articles.json'; $this->assertEquals($expected, $result); $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'ext' => 'json')); $expected = '/articles.json'; $this->assertEquals($expected, $result); } /** * testPluginUrlGeneration method * * @return void */ public function testUrlGenerationPlugins() { $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => 'test', 'controller' => 'controller', 'action' => 'index' ))->addPaths(array( 'base' => '/base', 'here' => '/clients/sage/portal/donations', 'webroot' => '/base/' )) ); $this->assertEquals(Router::url('read/1'), '/base/test/controller/read/1'); Router::reload(); Router::connect('/:lang/:plugin/:controller/*', array('action' => 'index')); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'lang' => 'en', 'plugin' => 'shows', 'controller' => 'shows', 'action' => 'index', 'url' => array('url' => 'en/shows/'), ))->addPaths(array( 'base' => '', 'here' => '/en/shows', 'webroot' => '/' )) ); Router::parse('/en/shows/'); $result = Router::url(array( 'lang' => 'en', 'controller' => 'shows', 'action' => 'index', 'page' => '1', )); $expected = '/en/shows/shows/page:1'; $this->assertEquals($expected, $result); } /** * test that you can leave active plugin routes with plugin = null * * @return void */ public function testCanLeavePlugin() { Router::reload(); Router::connect( '/admin/other/:controller/:action/*', array( 'admin' => 1, 'plugin' => 'aliased', 'prefix' => 'admin' ) ); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'pass' => array(), 'admin' => true, 'prefix' => 'admin', 'plugin' => 'this', 'action' => 'admin_index', 'controller' => 'interesting', 'url' => array('url' => 'admin/this/interesting/index'), ))->addPaths(array( 'base' => '', 'here' => '/admin/this/interesting/index', 'webroot' => '/', )) ); $result = Router::url(array('plugin' => null, 'controller' => 'posts', 'action' => 'index')); $this->assertEquals('/admin/posts', $result); $result = Router::url(array('controller' => 'posts', 'action' => 'index')); $this->assertEquals('/admin/this/posts', $result); $result = Router::url(array('plugin' => 'aliased', 'controller' => 'posts', 'action' => 'index')); $this->assertEquals('/admin/other/posts/index', $result); } /** * Test that URL's fail to parse when they are prefixed with // * * @return void */ public function testUrlParseFailureDoubleSlash() { Router::connect('/posts', array('controller' => 'posts', 'action' => 'index')); $result = Router::parse('/posts'); $this->assertEquals( array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'index'), $result ); $result = Router::parse('//posts'); $this->assertEquals(array(), $result); } /** * testUrlParsing method * * @return void */ public function testUrlParsing() { extract(Router::getNamedExpressions()); Router::connect('/posts/:value/:somevalue/:othervalue/*', array('controller' => 'posts', 'action' => 'view'), array('value', 'somevalue', 'othervalue')); $result = Router::parse('/posts/2007/08/01/title-of-post-here'); $expected = array('value' => '2007', 'somevalue' => '08', 'othervalue' => '01', 'controller' => 'posts', 'action' => 'view', 'plugin' => '', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/:year/:month/:day/*', array('controller' => 'posts', 'action' => 'view'), array('year' => $Year, 'month' => $Month, 'day' => $Day)); $result = Router::parse('/posts/2007/08/01/title-of-post-here'); $expected = array('year' => '2007', 'month' => '08', 'day' => '01', 'controller' => 'posts', 'action' => 'view', 'plugin' => '', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/:day/:year/:month/*', array('controller' => 'posts', 'action' => 'view'), array('year' => $Year, 'month' => $Month, 'day' => $Day)); $result = Router::parse('/posts/01/2007/08/title-of-post-here'); $expected = array('day' => '01', 'year' => '2007', 'month' => '08', 'controller' => 'posts', 'action' => 'view', 'plugin' => '', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/:month/:day/:year/*', array('controller' => 'posts', 'action' => 'view'), array('year' => $Year, 'month' => $Month, 'day' => $Day)); $result = Router::parse('/posts/08/01/2007/title-of-post-here'); $expected = array('month' => '08', 'day' => '01', 'year' => '2007', 'controller' => 'posts', 'action' => 'view', 'plugin' => '', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/:year/:month/:day/*', array('controller' => 'posts', 'action' => 'view')); $result = Router::parse('/posts/2007/08/01/title-of-post-here'); $expected = array('year' => '2007', 'month' => '08', 'day' => '01', 'controller' => 'posts', 'action' => 'view', 'plugin' => '', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $result = Router::parse('/pages/display/home'); $expected = array('plugin' => null, 'pass' => array('home'), 'controller' => 'pages', 'action' => 'display', 'named' => array()); $this->assertEquals($expected, $result); $result = Router::parse('pages/display/home/'); $this->assertEquals($expected, $result); $result = Router::parse('pages/display/home'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/page/*', array('controller' => 'test')); $result = Router::parse('/page/my-page'); $expected = array('pass' => array('my-page'), 'plugin' => null, 'controller' => 'test', 'action' => 'index', 'named' => array()); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:language/contact', array('language' => 'eng', 'plugin' => 'contact', 'controller' => 'contact', 'action' => 'index'), array('language' => '[a-z]{3}')); $result = Router::parse('/eng/contact'); $expected = array('pass' => array(), 'named' => array(), 'language' => 'eng', 'plugin' => 'contact', 'controller' => 'contact', 'action' => 'index'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/forestillinger/:month/:year/*', array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'), array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}') ); $result = Router::parse('/forestillinger/10/2007/min-forestilling'); $expected = array('pass' => array('min-forestilling'), 'plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'year' => 2007, 'month' => 10, 'named' => array()); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:controller/:action/*'); Router::connect('/', array('plugin' => 'pages', 'controller' => 'pages', 'action' => 'display')); $result = Router::parse('/'); $expected = array('pass' => array(), 'named' => array(), 'controller' => 'pages', 'action' => 'display', 'plugin' => 'pages'); $this->assertEquals($expected, $result); $result = Router::parse('/posts/edit/0'); $expected = array('pass' => array(0), 'named' => array(), 'controller' => 'posts', 'action' => 'edit', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/:id::url_title', array('controller' => 'posts', 'action' => 'view'), array('pass' => array('id', 'url_title'), 'id' => '[\d]+')); $result = Router::parse('/posts/5:sample-post-title'); $expected = array('pass' => array('5', 'sample-post-title'), 'named' => array(), 'id' => 5, 'url_title' => 'sample-post-title', 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/:id::url_title/*', array('controller' => 'posts', 'action' => 'view'), array('pass' => array('id', 'url_title'), 'id' => '[\d]+')); $result = Router::parse('/posts/5:sample-post-title/other/params/4'); $expected = array('pass' => array('5', 'sample-post-title', 'other', 'params', '4'), 'named' => array(), 'id' => 5, 'url_title' => 'sample-post-title', 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/:url_title-(uuid::id)', array('controller' => 'posts', 'action' => 'view'), array('pass' => array('id', 'url_title'), 'id' => $UUID)); $result = Router::parse('/posts/sample-post-title-(uuid:47fc97a9-019c-41d1-a058-1fa3cbdd56cb)'); $expected = array('pass' => array('47fc97a9-019c-41d1-a058-1fa3cbdd56cb', 'sample-post-title'), 'named' => array(), 'id' => '47fc97a9-019c-41d1-a058-1fa3cbdd56cb', 'url_title' => 'sample-post-title', 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => false)); $result = Router::parse('/posts/view/foo:bar/routing:fun'); $expected = array('pass' => array('foo:bar', 'routing:fun'), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => array('foo', 'answer'))); $result = Router::parse('/posts/view/foo:bar/routing:fun/answer:42'); $expected = array('pass' => array('routing:fun'), 'named' => array('foo' => 'bar', 'answer' => '42'), 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => array('foo', 'answer'), 'greedyNamed' => true)); $result = Router::parse('/posts/view/foo:bar/routing:fun/answer:42'); $expected = array('pass' => array(), 'named' => array('foo' => 'bar', 'routing' => 'fun', 'answer' => '42'), 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => array('foo', 'answer'), 'greedyNamed' => true)); $result = Router::parse('/posts/view/foo:bar/routing:fun/answer:42?id=123&tab=abc'); $expected = array('pass' => array(), 'named' => array('foo' => 'bar', 'routing' => 'fun', 'answer' => '42'), 'plugin' => null, 'controller' => 'posts', 'action' => 'view', '?' => array('id' => '123', 'tab' => 'abc')); $this->assertEquals($expected, $result); } /** * test that the persist key works. * * @return void */ public function testPersistentParameters() { Router::reload(); Router::connect( '/:lang/:color/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('persist' => array('lang', 'color')) ); Router::connect( '/:lang/:color/posts/index', array('controller' => 'posts', 'action' => 'index'), array('persist' => array('lang')) ); Router::connect('/:lang/:color/posts/edit/*', array('controller' => 'posts', 'action' => 'edit')); Router::connect('/about', array('controller' => 'pages', 'action' => 'view', 'about')); Router::parse('/en/red/posts/view/5'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'lang' => 'en', 'color' => 'red', 'prefix' => 'admin', 'plugin' => null, 'action' => 'view', 'controller' => 'posts', ))->addPaths(array( 'base' => '/', 'here' => '/en/red/posts/view/5', 'webroot' => '/', )) ); $expected = '/en/red/posts/view/6'; $result = Router::url(array('controller' => 'posts', 'action' => 'view', 6)); $this->assertEquals($expected, $result); $expected = '/en/blue/posts/index'; $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'color' => 'blue')); $this->assertEquals($expected, $result); $expected = '/posts/edit/6'; $result = Router::url(array('controller' => 'posts', 'action' => 'edit', 6, 'color' => null, 'lang' => null)); $this->assertEquals($expected, $result); $expected = '/posts'; $result = Router::url(array('controller' => 'posts', 'action' => 'index')); $this->assertEquals($expected, $result); $expected = '/posts/edit/7'; $result = Router::url(array('controller' => 'posts', 'action' => 'edit', 7)); $this->assertEquals($expected, $result); $expected = '/about'; $result = Router::url(array('controller' => 'pages', 'action' => 'view', 'about')); $this->assertEquals($expected, $result); } /** * testUuidRoutes method * * @return void */ public function testUuidRoutes() { Router::connect( '/subjects/add/:category_id', array('controller' => 'subjects', 'action' => 'add'), array('category_id' => '\w{8}-\w{4}-\w{4}-\w{4}-\w{12}') ); $result = Router::parse('/subjects/add/4795d601-19c8-49a6-930e-06a8b01d17b7'); $expected = array('pass' => array(), 'named' => array(), 'category_id' => '4795d601-19c8-49a6-930e-06a8b01d17b7', 'plugin' => null, 'controller' => 'subjects', 'action' => 'add'); $this->assertEquals($expected, $result); } /** * testRouteSymmetry method * * @return void */ public function testRouteSymmetry() { Router::connect( "/:extra/page/:slug/*", array('controller' => 'pages', 'action' => 'view', 'extra' => null), array("extra" => '[a-z1-9_]*', "slug" => '[a-z1-9_]+', "action" => 'view') ); $result = Router::parse('/some_extra/page/this_is_the_slug'); $expected = array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => 'some_extra'); $this->assertEquals($expected, $result); $result = Router::parse('/page/this_is_the_slug'); $expected = array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => null); $this->assertEquals($expected, $result); Router::reload(); Router::connect( "/:extra/page/:slug/*", array('controller' => 'pages', 'action' => 'view', 'extra' => null), array("extra" => '[a-z1-9_]*', "slug" => '[a-z1-9_]+') ); Router::parse('/'); $result = Router::url(array('admin' => null, 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => null)); $expected = '/page/this_is_the_slug'; $this->assertEquals($expected, $result); $result = Router::url(array('admin' => null, 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => 'some_extra')); $expected = '/some_extra/page/this_is_the_slug'; $this->assertEquals($expected, $result); } /** * Test parse and reverse symmetry * * @return void * @dataProvider parseReverseSymmetryData */ public function testParseReverseSymmetry($url) { $this->assertSame($url, Router::reverse(Router::parse($url) + array('url' => array()))); } /** * Data for parse and reverse test * * @return array */ public function parseReverseSymmetryData() { return array( array('/'), array('/controller/action'), array('/controller/action/param'), array('/controller/action?param1=value1¶m2=value2'), array('/controller/action/param?param1=value1'), array('/controller/action/named1:nv1'), array('/controller/action/named1:nv1?param1=value1') ); } /** * Test that Routing.prefixes are used when a Router instance is created * or reset * * @return void */ public function testRoutingPrefixesSetting() { $restore = Configure::read('Routing'); Configure::write('Routing.prefixes', array('admin', 'member', 'super_user')); Router::reload(); $result = Router::prefixes(); $expected = array('admin', 'member', 'super_user'); $this->assertEquals($expected, $result); Configure::write('Routing.prefixes', array('admin', 'member')); Router::reload(); $result = Router::prefixes(); $expected = array('admin', 'member'); $this->assertEquals($expected, $result); Configure::write('Routing', $restore); } /** * Test prefix routing and plugin combinations * * @return void */ public function testPrefixRoutingAndPlugins() { Configure::write('Routing.prefixes', array('admin')); $paths = App::path('plugins'); App::build(array( 'plugins' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS ) ), App::RESET); CakePlugin::load(array('TestPlugin')); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'admin' => true, 'controller' => 'controller', 'action' => 'action', 'plugin' => null, 'prefix' => 'admin' ))->addPaths(array( 'base' => '/', 'here' => '/', 'webroot' => '/base/', )) ); Router::parse('/'); $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index')); $expected = '/admin/test_plugin'; $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => 'test_plugin', 'controller' => 'show_tickets', 'action' => 'admin_edit', 'pass' => array('6'), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/shows/show_tickets/edit/6') ))->addPaths(array( 'base' => '/', 'here' => '/admin/shows/show_tickets/edit/6', 'webroot' => '/', )) ); $result = Router::url(array( 'plugin' => 'test_plugin', 'controller' => 'show_tickets', 'action' => 'edit', 6, 'admin' => true, 'prefix' => 'admin' )); $expected = '/admin/test_plugin/show_tickets/edit/6'; $this->assertEquals($expected, $result); $result = Router::url(array( 'plugin' => 'test_plugin', 'controller' => 'show_tickets', 'action' => 'index', 'admin' => true )); $expected = '/admin/test_plugin/show_tickets'; $this->assertEquals($expected, $result); App::build(array('plugins' => $paths)); } /** * testParseExtensions method * * @return void */ public function testParseExtensions() { $this->assertEquals(array(), Router::extensions()); Router::parseExtensions('rss'); $this->assertEquals(array('rss'), Router::extensions()); } /** * testSetExtensions method * * @return void */ public function testSetExtensions() { Router::setExtensions(array('rss')); $this->assertEquals(array('rss'), Router::extensions()); require CAKE . 'Config' . DS . 'routes.php'; $result = Router::parse('/posts.rss'); $this->assertFalse(isset($result['ext'])); Router::parseExtensions(); $result = Router::parse('/posts.rss'); $this->assertEquals('rss', $result['ext']); $result = Router::parse('/posts.xml'); $this->assertFalse(isset($result['ext'])); Router::setExtensions(array('xml')); $result = Router::extensions(); $this->assertEquals(array('rss', 'xml'), $result); $result = Router::parse('/posts.xml'); $this->assertEquals('xml', $result['ext']); $result = Router::setExtensions(array('pdf'), false); $this->assertEquals(array('pdf'), $result); } /** * testExtensionParsing method * * @return void */ public function testExtensionParsing() { Router::parseExtensions(); require CAKE . 'Config' . DS . 'routes.php'; $result = Router::parse('/posts.rss'); $expected = array('plugin' => null, 'controller' => 'posts', 'action' => 'index', 'ext' => 'rss', 'pass' => array(), 'named' => array()); $this->assertEquals($expected, $result); $result = Router::parse('/posts/view/1.rss'); $expected = array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'pass' => array('1'), 'named' => array(), 'ext' => 'rss'); $this->assertEquals($expected, $result); $result = Router::parse('/posts/view/1.rss?query=test'); $expected['?'] = array('query' => 'test'); $this->assertEquals($expected, $result); $result = Router::parse('/posts/view/1.atom'); unset($expected['?']); $expected['ext'] = 'atom'; $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; Router::parseExtensions('rss', 'xml'); $result = Router::parse('/posts.xml'); $expected = array('plugin' => null, 'controller' => 'posts', 'action' => 'index', 'ext' => 'xml', 'pass' => array(), 'named' => array()); $this->assertEquals($expected, $result); $result = Router::parse('/posts.atom?hello=goodbye'); $expected = array('plugin' => null, 'controller' => 'posts.atom', 'action' => 'index', 'pass' => array(), 'named' => array(), '?' => array('hello' => 'goodbye')); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/controller/action', array('controller' => 'controller', 'action' => 'action', 'ext' => 'rss')); $result = Router::parse('/controller/action'); $expected = array('controller' => 'controller', 'action' => 'action', 'plugin' => null, 'ext' => 'rss', 'named' => array(), 'pass' => array()); $this->assertEquals($expected, $result); Router::reload(); Router::parseExtensions('rss'); Router::connect('/controller/action', array('controller' => 'controller', 'action' => 'action', 'ext' => 'rss')); $result = Router::parse('/controller/action'); $expected = array('controller' => 'controller', 'action' => 'action', 'plugin' => null, 'ext' => 'rss', 'named' => array(), 'pass' => array()); $this->assertEquals($expected, $result); } /** * testQuerystringGeneration method * * @return void */ public function testQuerystringGeneration() { $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2')); $expected = '/posts/index/0?var=test&var2=test2'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => array('var' => 'test', 'var2' => 'test2'))); $this->assertEquals($expected, $result); $expected .= '&more=test+data'; $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => array('var' => 'test', 'var2' => 'test2', 'more' => 'test data'))); $this->assertEquals($expected, $result); // Test bug #4614 $restore = ini_get('arg_separator.output'); ini_set('arg_separator.output', '&'); $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => array('var' => 'test', 'var2' => 'test2', 'more' => 'test data'))); $this->assertEquals($expected, $result); ini_set('arg_separator.output', $restore); $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => array('var' => 'test', 'var2' => 'test2')), array('escape' => true)); $expected = '/posts/index/0?var=test&var2=test2'; $this->assertEquals($expected, $result); } /** * testConnectNamed method * * @return void */ public function testConnectNamed() { $named = Router::connectNamed(false, array('default' => true)); $this->assertFalse($named['greedyNamed']); $this->assertEquals(array_keys($named['rules']), $named['default']); Router::reload(); Router::connect('/foo/*', array('controller' => 'bar', 'action' => 'fubar')); Router::connectNamed(array(), array('separator' => '=')); $result = Router::parse('/foo/param1=value1/param2=value2'); $expected = array('pass' => array(), 'named' => array('param1' => 'value1', 'param2' => 'value2'), 'controller' => 'bar', 'action' => 'fubar', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/controller/action/*', array('controller' => 'controller', 'action' => 'action'), array('named' => array('param1' => 'value[\d]'))); Router::connectNamed(array(), array('greedy' => false, 'separator' => '=')); $result = Router::parse('/controller/action/param1=value1/param2=value2'); $expected = array('pass' => array('param2=value2'), 'named' => array('param1' => 'value1'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/:controller/:action/*'); Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); $result = Router::parse('/categories/index/limit=5'); $this->assertTrue(empty($result['named'])); } /** * testNamedArgsUrlGeneration method * * @return void */ public function testNamedArgsUrlGeneration() { $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 1, 'deleted' => 1)); $expected = '/posts/index/published:1/deleted:1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 0, 'deleted' => 0)); $expected = '/posts/index/published:0/deleted:0'; $this->assertEquals($expected, $result); Router::reload(); extract(Router::getNamedExpressions()); Router::connectNamed(array('file' => '[\w\.\-]+\.(html|png)')); Router::connect('/', array('controller' => 'graphs', 'action' => 'index')); Router::connect('/:id/*', array('controller' => 'graphs', 'action' => 'view'), array('id' => $ID)); $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 'id' => 12, 'file' => 'asdf.png')); $expected = '/12/file:asdf.png'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 12, 'file' => 'asdf.foo')); $expected = '/graphs/view/12/file:asdf.foo'; $this->assertEquals($expected, $result); Configure::write('Routing.prefixes', array('admin')); Router::reload(); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'admin' => true, 'controller' => 'controller', 'action' => 'index', 'plugin' => null ))->addPaths(array( 'base' => '/', 'here' => '/', 'webroot' => '/base/', )) ); Router::parse('/'); $result = Router::url(array('page' => 1, 0 => null, 'sort' => 'controller', 'direction' => 'asc', 'order' => null)); $expected = "/admin/controller/index/page:1/sort:controller/direction:asc"; $this->assertEquals($expected, $result); Router::reload(); $request = new CakeRequest('admin/controller/index'); $request->addParams(array( 'admin' => true, 'controller' => 'controller', 'action' => 'index', 'plugin' => null )); $request->base = '/'; Router::setRequestInfo($request); Router::parse('/admin/controller/index/type:whatever'); $result = Router::url(array('type' => 'new')); $expected = "/admin/controller/index/type:new"; $this->assertEquals($expected, $result); } /** * testNamedArgsUrlParsing method * * @return void */ public function testNamedArgsUrlParsing() { Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param:value'); $expected = array('pass' => array(), 'named' => array('param1' => 'value1:1', 'param2' => 'value2:3', 'param' => 'value'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $result = Router::connectNamed(false); $this->assertEquals(array(), array_keys($result['rules'])); $this->assertFalse($result['greedyNamed']); $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param:value'); $expected = array('pass' => array('param1:value1:1', 'param2:value2:3', 'param:value'), 'named' => array(), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $result = Router::connectNamed(true); $named = Router::namedConfig(); $this->assertEquals($named['default'], array_keys($result['rules'])); $this->assertTrue($result['greedyNamed']); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; Router::connectNamed(array('param1' => 'not-matching')); $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param:value'); $expected = array('pass' => array('param1:value1:1'), 'named' => array('param2' => 'value2:3', 'param' => 'value'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEquals($expected, $result); $result = Router::parse('/foo/view/param1:value1:1/param2:value2:3/param:value'); $expected = array('pass' => array('param1:value1:1'), 'named' => array('param2' => 'value2:3', 'param' => 'value'), 'controller' => 'foo', 'action' => 'view', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; Router::connectNamed(array('param1' => '[\d]', 'param2' => '[a-z]', 'param3' => '[\d]')); $result = Router::parse('/controller/action/param1:1/param2:2/param3:3'); $expected = array('pass' => array('param2:2'), 'named' => array('param1' => '1', 'param3' => '3'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; Router::connectNamed(array('param1' => '[\d]', 'param2' => true, 'param3' => '[\d]')); $result = Router::parse('/controller/action/param1:1/param2:2/param3:3'); $expected = array('pass' => array(), 'named' => array('param1' => '1', 'param2' => '2', 'param3' => '3'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; Router::connectNamed(array('param1' => 'value[\d]+:[\d]+'), array('greedy' => false)); $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param3:value'); $expected = array('pass' => array('param2:value2:3', 'param3:value'), 'named' => array('param1' => 'value1:1'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); $this->assertEquals($expected, $result); } /** * Test URL generation with legacy (1.2) style prefix routes. * * @return void * @see testUrlGenerationWithAutoPrefixes */ public function testUrlGenerationWithLegacyPrefixes() { Router::reload(); Router::connect('/protected/:controller/:action/*', array( 'prefix' => 'protected', 'protected' => true )); Router::parse('/'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'images', 'action' => 'index', 'prefix' => null, 'admin' => false, 'url' => array('url' => 'images/index') ))->addPaths(array( 'base' => '', 'here' => '/images/index', 'webroot' => '/', )) ); $result = Router::url(array('protected' => true)); $expected = '/protected/images/index'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'images', 'action' => 'add')); $expected = '/images/add'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true)); $expected = '/protected/images/add'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'edit', 1)); $expected = '/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); $expected = '/protected/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'protected_edit', 1, 'protected' => true)); $expected = '/protected/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); $expected = '/protected/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1)); $expected = '/others/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true)); $expected = '/protected/others/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); $expected = '/protected/others/edit/1/page:1'; $this->assertEquals($expected, $result); Router::connectNamed(array('random')); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); $expected = '/protected/others/edit/1/random:my-value'; $this->assertEquals($expected, $result); } /** * test newer style automatically generated prefix routes. * * @return void */ public function testUrlGenerationWithAutoPrefixes() { Configure::write('Routing.prefixes', array('protected')); Router::reload(); Router::parse('/'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'images', 'action' => 'index', 'prefix' => null, 'protected' => false, 'url' => array('url' => 'images/index') ))->addPaths(array( 'base' => '', 'here' => '/images/index', 'webroot' => '/', )) ); $result = Router::url(array('controller' => 'images', 'action' => 'add')); $expected = '/images/add'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true)); $expected = '/protected/images/add'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'images', 'action' => 'add_protected_test', 'protected' => true)); $expected = '/protected/images/add_protected_test'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'edit', 1)); $expected = '/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); $expected = '/protected/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'protected_edit', 1, 'protected' => true)); $expected = '/protected/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'protectededit', 1, 'protected' => true)); $expected = '/protected/images/protectededit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); $expected = '/protected/images/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1)); $expected = '/others/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true)); $expected = '/protected/others/edit/1'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); $expected = '/protected/others/edit/1/page:1'; $this->assertEquals($expected, $result); Router::connectNamed(array('random')); $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); $expected = '/protected/others/edit/1/random:my-value'; $this->assertEquals($expected, $result); } /** * test that auto-generated prefix routes persist * * @return void */ public function testAutoPrefixRoutePersistence() { Configure::write('Routing.prefixes', array('protected')); Router::reload(); Router::parse('/'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'images', 'action' => 'index', 'prefix' => 'protected', 'protected' => true, 'url' => array('url' => 'protected/images/index') ))->addPaths(array( 'base' => '', 'here' => '/protected/images/index', 'webroot' => '/', )) ); $result = Router::url(array('controller' => 'images', 'action' => 'add')); $expected = '/protected/images/add'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => false)); $expected = '/images/add'; $this->assertEquals($expected, $result); } /** * test that setting a prefix override the current one * * @return void */ public function testPrefixOverride() { Configure::write('Routing.prefixes', array('protected', 'admin')); Router::reload(); Router::parse('/'); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'images', 'action' => 'index', 'prefix' => 'protected', 'protected' => true, 'url' => array('url' => 'protected/images/index') ))->addPaths(array( 'base' => '', 'here' => '/protected/images/index', 'webroot' => '/', )) ); $result = Router::url(array('controller' => 'images', 'action' => 'add', 'admin' => true)); $expected = '/admin/images/add'; $this->assertEquals($expected, $result); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'images', 'action' => 'index', 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/images/index') ))->addPaths(array( 'base' => '', 'here' => '/admin/images/index', 'webroot' => '/', )) ); $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true)); $expected = '/protected/images/add'; $this->assertEquals($expected, $result); } /** * Test that setting a prefix to false is ignored, as its generally user error. * * @return void */ public function testPrefixFalseIgnored() { Configure::write('Routing.prefixes', array('admin')); Router::reload(); Router::connect('/cache_css/*', array('admin' => false, 'controller' => 'asset_compress', 'action' => 'get')); $url = Router::url(array('controller' => 'asset_compress', 'action' => 'get', 'test')); $expected = '/cache_css/test'; $this->assertEquals($expected, $url); $url = Router::url(array('admin' => false, 'controller' => 'asset_compress', 'action' => 'get', 'test')); $expected = '/cache_css/test'; $this->assertEquals($expected, $url); $url = Router::url(array('admin' => true, 'controller' => 'asset_compress', 'action' => 'get', 'test')); $this->assertEquals('/admin/asset_compress/get/test', $url); } /** * testRemoveBase method * * @return void */ public function testRemoveBase() { $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'controller', 'action' => 'index', 'bare' => 0, 'url' => array('url' => 'protected/images/index') ))->addPaths(array( 'base' => '/base', 'here' => '/', 'webroot' => '/base/', )) ); $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action')); $expected = '/base/my_controller/my_action'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action', 'base' => false)); $expected = '/my_controller/my_action'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action', 'base' => true)); $expected = '/base/my_controller/my_action/base:1'; $this->assertEquals($expected, $result); } /** * testPagesUrlParsing method * * @return void */ public function testPagesUrlParsing() { Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); $result = Router::parse('/'); $expected = array('pass' => array('home'), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); $this->assertEquals($expected, $result); $result = Router::parse('/pages/home/'); $expected = array('pass' => array('home'), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); $this->assertEquals($expected, $result); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); $result = Router::parse('/'); $expected = array('pass' => array('home'), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); $this->assertEquals($expected, $result); $result = Router::parse('/pages/display/home/event:value'); $expected = array('pass' => array('home'), 'named' => array('event' => 'value'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); $this->assertEquals($expected, $result); $result = Router::parse('/pages/display/home/event:Val_u2'); $expected = array('pass' => array('home'), 'named' => array('event' => 'Val_u2'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); $this->assertEquals($expected, $result); $result = Router::parse('/pages/display/home/event:val-ue'); $expected = array('pass' => array('home'), 'named' => array('event' => 'val-ue'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); $this->assertEquals($expected, $result); Router::reload(); Router::connect('/', array('controller' => 'posts', 'action' => 'index')); Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); $result = Router::parse('/pages/contact/'); $expected = array('pass' => array('contact'), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); $this->assertEquals($expected, $result); } /** * test that requests with a trailing dot don't loose the do. * * @return void */ public function testParsingWithTrailingPeriod() { Router::reload(); Router::connect('/:controller/:action/*'); $result = Router::parse('/posts/view/something.'); $this->assertEquals('something.', $result['pass'][0], 'Period was chopped off %s'); $result = Router::parse('/posts/view/something. . .'); $this->assertEquals('something. . .', $result['pass'][0], 'Period was chopped off %s'); } /** * test that requests with a trailing dot don't loose the do. * * @return void */ public function testParsingWithTrailingPeriodAndParseExtensions() { Router::reload(); Router::connect('/:controller/:action/*'); Router::parseExtensions('json'); $result = Router::parse('/posts/view/something.'); $this->assertEquals('something.', $result['pass'][0], 'Period was chopped off %s'); $result = Router::parse('/posts/view/something. . .'); $this->assertEquals('something. . .', $result['pass'][0], 'Period was chopped off %s'); } /** * test that patterns work for :action * * @return void */ public function testParsingWithPatternOnAction() { Router::reload(); Router::connect( '/blog/:action/*', array('controller' => 'blog_posts'), array('action' => 'other|actions') ); $result = Router::parse('/blog/other'); $expected = array( 'plugin' => null, 'controller' => 'blog_posts', 'action' => 'other', 'pass' => array(), 'named' => array() ); $this->assertEquals($expected, $result); $result = Router::parse('/blog/foobar'); $this->assertSame(array(), $result); $result = Router::url(array('controller' => 'blog_posts', 'action' => 'foo')); $this->assertEquals('/blog_posts/foo', $result); $result = Router::url(array('controller' => 'blog_posts', 'action' => 'actions')); $this->assertEquals('/blog/actions', $result); } /** * testParsingWithPrefixes method * * @return void */ public function testParsingWithPrefixes() { $adminParams = array('prefix' => 'admin', 'admin' => true); Router::connect('/admin/:controller', $adminParams); Router::connect('/admin/:controller/:action', $adminParams); Router::connect('/admin/:controller/:action/*', $adminParams); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'controller', 'action' => 'index' ))->addPaths(array( 'base' => '/base', 'here' => '/', 'webroot' => '/base/', )) ); $result = Router::parse('/admin/posts/'); $expected = array('pass' => array(), 'named' => array(), 'prefix' => 'admin', 'plugin' => null, 'controller' => 'posts', 'action' => 'admin_index', 'admin' => true); $this->assertEquals($expected, $result); $result = Router::parse('/admin/posts'); $this->assertEquals($expected, $result); $result = Router::url(array('admin' => true, 'controller' => 'posts')); $expected = '/base/admin/posts'; $this->assertEquals($expected, $result); $result = Router::prefixes(); $expected = array('admin'); $this->assertEquals($expected, $result); Router::reload(); $prefixParams = array('prefix' => 'members', 'members' => true); Router::connect('/members/:controller', $prefixParams); Router::connect('/members/:controller/:action', $prefixParams); Router::connect('/members/:controller/:action/*', $prefixParams); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'controller', 'action' => 'index', 'bare' => 0 ))->addPaths(array( 'base' => '/base', 'here' => '/', 'webroot' => '/', )) ); $result = Router::parse('/members/posts/index'); $expected = array('pass' => array(), 'named' => array(), 'prefix' => 'members', 'plugin' => null, 'controller' => 'posts', 'action' => 'members_index', 'members' => true); $this->assertEquals($expected, $result); $result = Router::url(array('members' => true, 'controller' => 'posts', 'action' => 'index', 'page' => 2)); $expected = '/base/members/posts/index/page:2'; $this->assertEquals($expected, $result); $result = Router::url(array('members' => true, 'controller' => 'users', 'action' => 'add')); $expected = '/base/members/users/add'; $this->assertEquals($expected, $result); } /** * Tests URL generation with flags and prefixes in and out of context * * @return void */ public function testUrlWritingWithPrefixes() { Router::connect('/company/:controller/:action/*', array('prefix' => 'company', 'company' => true)); Router::connect('/login', array('controller' => 'users', 'action' => 'login')); $result = Router::url(array('controller' => 'users', 'action' => 'login', 'company' => true)); $expected = '/company/users/login'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'users', 'action' => 'company_login', 'company' => true)); $expected = '/company/users/login'; $this->assertEquals($expected, $result); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'users', 'action' => 'login', 'company' => true ))->addPaths(array( 'base' => '/', 'here' => '/', 'webroot' => '/base/', )) ); $result = Router::url(array('controller' => 'users', 'action' => 'login', 'company' => false)); $expected = '/login'; $this->assertEquals($expected, $result); } /** * test url generation with prefixes and custom routes * * @return void */ public function testUrlWritingWithPrefixesAndCustomRoutes() { Router::connect( '/admin/login', array('controller' => 'users', 'action' => 'login', 'prefix' => 'admin', 'admin' => true) ); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'posts', 'action' => 'index', 'admin' => true, 'prefix' => 'admin' ))->addPaths(array( 'base' => '/', 'here' => '/', 'webroot' => '/', )) ); $result = Router::url(array('controller' => 'users', 'action' => 'login', 'admin' => true)); $this->assertEquals('/admin/login', $result); $result = Router::url(array('controller' => 'users', 'action' => 'login')); $this->assertEquals('/admin/login', $result); $result = Router::url(array('controller' => 'users', 'action' => 'admin_login')); $this->assertEquals('/admin/login', $result); } /** * testPassedArgsOrder method * * @return void */ public function testPassedArgsOrder() { Router::connect('/test-passed/*', array('controller' => 'pages', 'action' => 'display', 'home')); Router::connect('/test2/*', array('controller' => 'pages', 'action' => 'display', 2)); Router::connect('/test/*', array('controller' => 'pages', 'action' => 'display', 1)); Router::parse('/'); $result = Router::url(array('controller' => 'pages', 'action' => 'display', 1, 'whatever')); $expected = '/test/whatever'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'pages', 'action' => 'display', 2, 'whatever')); $expected = '/test2/whatever'; $this->assertEquals($expected, $result); $result = Router::url(array('controller' => 'pages', 'action' => 'display', 'home', 'whatever')); $expected = '/test-passed/whatever'; $this->assertEquals($expected, $result); Configure::write('Routing.prefixes', array('admin')); Router::reload(); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'images', 'action' => 'index', 'url' => array('url' => 'protected/images/index') ))->addPaths(array( 'base' => '', 'here' => '/protected/images/index', 'webroot' => '/', )) ); Router::connect('/protected/:controller/:action/*', array( 'controller' => 'users', 'action' => 'index', 'prefix' => 'protected' )); Router::parse('/'); $result = Router::url(array('controller' => 'images', 'action' => 'add')); $expected = '/protected/images/add'; $this->assertEquals($expected, $result); $result = Router::prefixes(); $expected = array('admin', 'protected'); $this->assertEquals($expected, $result); } /** * testRegexRouteMatching method * * @return void */ public function testRegexRouteMatching() { Router::connect('/:locale/:controller/:action/*', array(), array('locale' => 'dan|eng')); $result = Router::parse('/eng/test/test_action'); $expected = array('pass' => array(), 'named' => array(), 'locale' => 'eng', 'controller' => 'test', 'action' => 'test_action', 'plugin' => null); $this->assertEquals($expected, $result); $result = Router::parse('/badness/test/test_action'); $this->assertSame(array(), $result); Router::reload(); Router::connect('/:locale/:controller/:action/*', array(), array('locale' => 'dan|eng')); $request = new CakeRequest(); Router::setRequestInfo( $request->addParams(array( 'plugin' => null, 'controller' => 'test', 'action' => 'index', 'url' => array('url' => 'test/test_action') ))->addPaths(array( 'base' => '', 'here' => '/test/test_action', 'webroot' => '/', )) ); $result = Router::url(array('action' => 'test_another_action')); $expected = '/test/test_another_action'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'test_another_action', 'locale' => 'eng')); $expected = '/eng/test/test_another_action'; $this->assertEquals($expected, $result); $result = Router::url(array('action' => 'test_another_action', 'locale' => 'badness')); $expected = '/test/test_another_action/locale:badness'; $this->assertEquals($expected, $result); } /** * testStripPlugin * * @return void */ public function testStripPlugin() { $pluginName = 'forums'; $url = 'example.com/' . $pluginName . '/'; $expected = 'example.com'; $this->assertEquals($expected, Router::stripPlugin($url, $pluginName)); $this->assertEquals(Router::stripPlugin($url), $url); $this->assertEquals(Router::stripPlugin($url, null), $url); } /** * testCurrentRouteWhenNonExistentRoute * * @return void */ public function testCurrentRouteWhenNonExistentRoute() { $route = Router::currentRoute(); $this->assertFalse($route); } /** * testCurrentRoute * * This test needs some improvement and actual requestAction() usage * * @return void */ public function testCurrentRoute() { $url = array('controller' => 'pages', 'action' => 'display', 'government'); Router::connect('/government', $url); Router::parse('/government'); $route = Router::currentRoute(); $this->assertEquals(array_merge($url, array('plugin' => null)), $route->defaults); } /** * testRequestRoute * * @return void */ public function testRequestRoute() { $url = array('controller' => 'products', 'action' => 'display', 5); Router::connect('/government', $url); Router::parse('/government'); $route = Router::requestRoute(); $this->assertEquals(array_merge($url, array('plugin' => null)), $route->defaults); // test that the first route is matched Router::connect('/government', $url); Router::parse('/government'); $route = Router::requestRoute(); $this->assertEquals(array_merge($url, array('plugin' => null)), $route->defaults); // test that an unmatched route does not change the current route Router::connect('/actor', $url); Router::parse('/government'); $route = Router::requestRoute(); $this->assertEquals(array_merge($url, array('plugin' => null)), $route->defaults); } /** * testGetParams * * @return void */ public function testGetParams() { $paths = array('base' => '/', 'here' => '/products/display/5', 'webroot' => '/webroot'); $params = array('param1' => '1', 'param2' => '2'); Router::setRequestInfo(array($params, $paths)); $expected = array( 'plugin' => null, 'controller' => false, 'action' => false, 'named' => array(), 'pass' => array(), 'param1' => '1', 'param2' => '2', ); $this->assertEquals($expected, Router::getParams()); $this->assertEquals(false, Router::getParam('controller')); $this->assertEquals('1', Router::getParam('param1')); $this->assertEquals('2', Router::getParam('param2')); Router::reload(); $params = array('controller' => 'pages', 'action' => 'display'); Router::setRequestInfo(array($params, $paths)); $expected = array( 'plugin' => null, 'controller' => 'pages', 'action' => 'display', 'named' => array(), 'pass' => array(), ); $this->assertEquals($expected, Router::getParams()); $this->assertEquals($expected, Router::getParams(true)); } /** * test that connectDefaults() can disable default route connection * * @return void */ public function testDefaultsMethod() { Router::connect('/test/*', array('controller' => 'pages', 'action' => 'display', 2)); $result = Router::parse('/posts/edit/5'); $this->assertFalse(isset($result['controller'])); $this->assertFalse(isset($result['action'])); } /** * test that the required default routes are connected. * * @return void */ public function testConnectDefaultRoutes() { App::build(array( 'plugins' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS ) ), App::RESET); CakePlugin::load(array('TestPlugin', 'PluginJs')); Router::reload(); require CAKE . 'Config' . DS . 'routes.php'; $result = Router::url(array('plugin' => 'plugin_js', 'controller' => 'js_file', 'action' => 'index')); $this->assertEquals('/plugin_js/js_file', $result); $result = Router::parse('/plugin_js/js_file'); $expected = array( 'plugin' => 'plugin_js', 'controller' => 'js_file', 'action' => 'index', 'named' => array(), 'pass' => array() ); $this->assertEquals($expected, $result); $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index')); $this->assertEquals('/test_plugin', $result); $result = Router::parse('/test_plugin'); $expected = array( 'plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index', 'named' => array(), 'pass' => array() ); $this->assertEquals($expected, $result, 'Plugin shortcut route broken. %s'); } /** * test using a custom route class for route connection * * @return void */ public function testUsingCustomRouteClass() { $this->getMock('CakeRoute', array(), array(), 'MockConnectedRoute', false); $routes = Router::connect( '/:slug', array('controller' => 'posts', 'action' => 'view'), array('routeClass' => 'MockConnectedRoute', 'slug' => '[a-z_-]+') ); $this->assertInstanceOf('MockConnectedRoute', $routes[0], 'Incorrect class used. %s'); $expected = array('controller' => 'posts', 'action' => 'view', 'slug' => 'test'); $routes[0]->expects($this->any()) ->method('parse') ->will($this->returnValue($expected)); $result = Router::parse('/test'); $this->assertEquals($expected, $result); } /** * test using custom route class in PluginDot notation * * @return void */ public function testUsingCustomRouteClassPluginDotSyntax() { App::build(array( 'Plugin' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS ) )); CakePlugin::load('TestPlugin'); App::uses('TestRoute', 'TestPlugin.Routing/Route'); $routes = Router::connect( '/:slug', array('controller' => 'posts', 'action' => 'view'), array('routeClass' => 'TestPlugin.TestRoute', 'slug' => '[a-z_-]+') ); $this->assertInstanceOf('TestRoute', $routes[0]); CakePlugin::unload('TestPlugin'); } /** * test that route classes must extend CakeRoute * * @expectedException RouterException * @return void */ public function testCustomRouteException() { Router::connect('/:controller', array(), array('routeClass' => 'CakeObject')); } /** * test reversing parameter arrays back into strings. * * Mark the router as initialized so it doesn't auto-load routes * * @return void */ public function testReverseToken() { Router::$initialized = true; $params = array( 'controller' => 'posts', 'action' => 'view', 'pass' => array(1), 'named' => array(), 'url' => array(), 'autoRender' => 1, 'bare' => 1, 'return' => 1, 'requested' => 1, '_Token' => array('key' => 'sekret') ); $result = Router::reverse($params); $this->assertEquals('/posts/view/1', $result); } public function testReverseNamed() { $params = array( 'controller' => 'posts', 'action' => 'index', 'pass' => array(1), 'named' => array('page' => 1, 'sort' => 'Article.title', 'direction' => 'desc'), 'url' => array(), ); $result = Router::reverse($params); $this->assertEquals('/posts/index/1/page:1/sort:Article.title/direction:desc', $result); } public function testReverseLocalized() { Router::connect('/:lang/:controller/:action/*', array(), array('lang' => '[a-z]{3}')); $params = array( 'lang' => 'eng', 'controller' => 'posts', 'action' => 'view', 'pass' => array(1), 'named' => array(), 'url' => array('url' => 'eng/posts/view/1'), ); $result = Router::reverse($params); $this->assertEquals('/eng/posts/view/1', $result); } public function testReverseArrayQuery() { Router::connect('/:lang/:controller/:action/*', array(), array('lang' => '[a-z]{3}')); $params = array( 'lang' => 'eng', 'controller' => 'posts', 'action' => 'view', 'pass' => array(1), 'named' => array(), 'url' => array('url' => 'eng/posts/view/1', 'foo' => 'bar', 'baz' => 'quu'), 'paging' => array(), 'models' => array(), ); $result = Router::reverse($params); $this->assertEquals('/eng/posts/view/1?foo=bar&baz=quu', $result); } public function testReverseCakeRequestQuery() { Router::connect('/:lang/:controller/:action/*', array(), array('lang' => '[a-z]{3}')); $request = new CakeRequest('/eng/posts/view/1'); $request->addParams(array( 'lang' => 'eng', 'controller' => 'posts', 'action' => 'view', 'pass' => array(1), 'named' => array(), )); $request->query = array('url' => 'eng/posts/view/1', 'test' => 'value'); $result = Router::reverse($request); $expected = '/eng/posts/view/1?test=value'; $this->assertEquals($expected, $result); } public function testReverseFull() { Router::connect('/:lang/:controller/:action/*', array(), array('lang' => '[a-z]{3}')); $params = array( 'lang' => 'eng', 'controller' => 'posts', 'action' => 'view', 'pass' => array(1), 'named' => array(), 'url' => array('url' => 'eng/posts/view/1'), ); $result = Router::reverse($params, true); $this->assertRegExp('/^http(s)?:\/\//', $result); } public function testReverseToArrayNamed() { $params = array( 'controller' => 'posts', 'action' => 'index', 'pass' => array(123), 'named' => array('page' => 123, 'sort' => 'Article.title', 'direction' => 'desc'), 'url' => array(), ); $result = Router::reverseToArray($params); $expected = array( 'controller' => 'posts', 'action' => 'index', 123, 'page' => 123, 'sort' => 'Article.title', 'direction' => 'desc', ); $this->assertEquals($expected, $result); } public function testReverseToArrayCakeRequestQuery() { $request = new CakeRequest('/posts/view/123'); $request->addParams(array( 'controller' => 'posts', 'action' => 'view', 'pass' => array(123), 'named' => array(), )); $request->query = array('url' => 'eng/posts/view/123', 'test' => 'value'); $result = Router::reverseToArray($request); $expected = array( 'plugin' => null, 'controller' => 'posts', 'action' => 'view', 123, '?' => array( 'test' => 'value', ), ); $this->assertEquals($expected, $result); } /** * Test that extensions work with Router::reverse() * * @return void */ public function testReverseWithExtension() { Router::parseExtensions('json'); $request = new CakeRequest('/posts/view/1.json'); $request->addParams(array( 'controller' => 'posts', 'action' => 'view', 'pass' => array(1), 'named' => array(), 'ext' => 'json', )); $request->query = array(); $result = Router::reverse($request); $expected = '/posts/view/1.json'; $this->assertEquals($expected, $result); } /** * test that setRequestInfo can accept arrays and turn that into a CakeRequest object. * * @return void */ public function testSetRequestInfoLegacy() { Router::setRequestInfo(array( array( 'plugin' => null, 'controller' => 'images', 'action' => 'index', 'url' => array('url' => 'protected/images/index') ), array( 'base' => '', 'here' => '/protected/images/index', 'webroot' => '/', ) )); $result = Router::getRequest(); $this->assertEquals('images', $result->controller); $this->assertEquals('index', $result->action); $this->assertEquals('', $result->base); $this->assertEquals('/protected/images/index', $result->here); $this->assertEquals('/', $result->webroot); } /** * Test that Router::url() uses the first request * * @return void */ public function testUrlWithRequestAction() { $firstRequest = new CakeRequest('/posts/index'); $firstRequest->addParams(array( 'plugin' => null, 'controller' => 'posts', 'action' => 'index' ))->addPaths(array('base' => '')); $secondRequest = new CakeRequest('/posts/index'); $secondRequest->addParams(array( 'requested' => 1, 'plugin' => null, 'controller' => 'comments', 'action' => 'listing' ))->addPaths(array('base' => '')); Router::setRequestInfo($firstRequest); Router::setRequestInfo($secondRequest); $result = Router::url(array('base' => false)); $this->assertEquals('/comments/listing', $result, 'with second requests, the last should win.'); Router::popRequest(); $result = Router::url(array('base' => false)); $this->assertEquals('/posts', $result, 'with second requests, the last should win.'); } /** * test that a route object returning a full URL is not modified. * * @return void */ public function testUrlFullUrlReturnFromRoute() { $url = 'http://example.com/posts/view/1'; $this->getMock('CakeRoute', array(), array('/'), 'MockReturnRoute'); $routes = Router::connect('/:controller/:action', array(), array('routeClass' => 'MockReturnRoute')); $routes[0]->expects($this->any())->method('match') ->will($this->returnValue($url)); $result = Router::url(array('controller' => 'posts', 'action' => 'view', 1)); $this->assertEquals($url, $result); } /** * test protocol in url * * @return void */ public function testUrlProtocol() { $url = 'http://example.com'; $this->assertEquals($url, Router::url($url)); $url = 'ed2k://example.com'; $this->assertEquals($url, Router::url($url)); $url = 'svn+ssh://example.com'; $this->assertEquals($url, Router::url($url)); $url = '://example.com'; $this->assertEquals($url, Router::url($url)); $url = '//example.com'; $this->assertEquals($url, Router::url($url)); $url = 'javascript:void(0)'; $this->assertEquals($url, Router::url($url)); $url = 'tel:012345-678'; $this->assertEquals($url, Router::url($url)); $url = 'sms:012345-678'; $this->assertEquals($url, Router::url($url)); $url = '#here'; $this->assertEquals($url, Router::url($url)); $url = '?param=0'; $this->assertEquals($url, Router::url($url)); $url = 'posts/index#here'; $expected = FULL_BASE_URL . '/posts/index#here'; $this->assertEquals($expected, Router::url($url, true)); } /** * Testing that patterns on the :action param work properly. * * @return void */ public function testPatternOnAction() { $route = new CakeRoute( '/blog/:action/*', array('controller' => 'blog_posts'), array('action' => 'other|actions') ); $result = $route->match(array('controller' => 'blog_posts', 'action' => 'foo')); $this->assertFalse($result); $result = $route->match(array('controller' => 'blog_posts', 'action' => 'actions')); $this->assertEquals('/blog/actions/', $result); $result = $route->parse('/blog/other'); $expected = array('controller' => 'blog_posts', 'action' => 'other', 'pass' => array(), 'named' => array()); $this->assertEquals($expected, $result); $result = $route->parse('/blog/foobar'); $this->assertFalse($result); } /** * Tests resourceMap as getter and setter. * * @return void */ public function testResourceMap() { $default = Router::resourceMap(); $expected = array( array('action' => 'index', 'method' => 'GET', 'id' => false), array('action' => 'view', 'method' => 'GET', 'id' => true), array('action' => 'add', 'method' => 'POST', 'id' => false), array('action' => 'edit', 'method' => 'PUT', 'id' => true), array('action' => 'delete', 'method' => 'DELETE', 'id' => true), array('action' => 'edit', 'method' => 'POST', 'id' => true) ); $this->assertEquals($expected, $default); $custom = array( array('action' => 'index', 'method' => 'GET', 'id' => false), array('action' => 'view', 'method' => 'GET', 'id' => true), array('action' => 'add', 'method' => 'POST', 'id' => false), array('action' => 'edit', 'method' => 'PUT', 'id' => true), array('action' => 'delete', 'method' => 'DELETE', 'id' => true), array('action' => 'update', 'method' => 'POST', 'id' => true) ); Router::resourceMap($custom); $this->assertEquals(Router::resourceMap(), $custom); Router::resourceMap($default); } /** * test setting redirect routes * * @return void */ public function testRouteRedirection() { Router::redirect('/blog', array('controller' => 'posts'), array('status' => 302)); $this->assertEquals(1, count(Router::$routes)); Router::$routes[0]->response = $this->getMock('CakeResponse', array('_sendHeader')); Router::$routes[0]->stop = false; $this->assertEquals(302, Router::$routes[0]->options['status']); Router::parse('/blog'); $header = Router::$routes[0]->response->header(); $this->assertEquals(Router::url('/posts', true), $header['Location']); $this->assertEquals(302, Router::$routes[0]->response->statusCode()); Router::$routes[0]->response = $this->getMock('CakeResponse', array('_sendHeader')); Router::parse('/not-a-match'); $this->assertEquals(array(), Router::$routes[0]->response->header()); } /** * Test setting the default route class * * @return void */ public function testDefaultRouteClass() { $this->getMock('CakeRoute', array(), array('/test'), 'TestDefaultRouteClass'); Router::defaultRouteClass('TestDefaultRouteClass'); $result = Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); $this->assertInstanceOf('TestDefaultRouteClass', $result[0]); } /** * Test getting the default route class * * @return void */ public function testDefaultRouteClassGetter() { $routeClass = 'TestDefaultRouteClass'; Router::defaultRouteClass($routeClass); $this->assertEquals($routeClass, Router::defaultRouteClass()); $this->assertEquals($routeClass, Router::defaultRouteClass(null)); } /** * Test that route classes must extend CakeRoute * * @expectedException RouterException * @return void */ public function testDefaultRouteException() { Router::defaultRouteClass(''); Router::connect('/:controller', array()); } /** * Test that route classes must extend CakeRoute * * @expectedException RouterException * @return void */ public function testSettingInvalidDefaultRouteException() { Router::defaultRouteClass('CakeObject'); } /** * Test that class must exist * * @expectedException RouterException * @return void */ public function testSettingNonExistentDefaultRouteException() { Router::defaultRouteClass('NonExistentClass'); } /** * Tests generating well-formed querystrings * * @return void */ public function testQueryString() { $result = Router::queryString(array('var' => 'foo bar')); $expected = '?var=foo+bar'; $this->assertEquals($expected, $result); $result = Router::queryString(false, array('some' => 'param', 'foo' => 'bar')); $expected = '?some=param&foo=bar'; $this->assertEquals($expected, $result); $existing = array('apple' => 'red', 'pear' => 'green'); $result = Router::queryString($existing, array('some' => 'param', 'foo' => 'bar')); $expected = '?apple=red&pear=green&some=param&foo=bar'; $this->assertEquals($expected, $result); $existing = 'apple=red&pear=green'; $result = Router::queryString($existing, array('some' => 'param', 'foo' => 'bar')); $expected = '?apple=red&pear=green&some=param&foo=bar'; $this->assertEquals($expected, $result); $existing = '?apple=red&pear=green'; $result = Router::queryString($existing, array('some' => 'param', 'foo' => 'bar')); $expected = '?apple=red&pear=green&some=param&foo=bar'; $this->assertEquals($expected, $result); $result = Router::queryString('apple=red&pear=green'); $expected = '?apple=red&pear=green'; $this->assertEquals($expected, $result); $result = Router::queryString('foo=bar', array('php' => 'nut', 'jose' => 'zap'), true); $expected = '?foo=bar&php=nut&jose=zap'; $this->assertEquals($expected, $result); $result = Router::queryString('foo=bar&', array('php' => 'nut', 'jose' => 'zap'), true); $expected = '?foo=bar&php=nut&jose=zap'; $this->assertEquals($expected, $result); $result = Router::queryString('foo=bar&', array('php' => 'nut', 'jose' => 'zap')); $expected = '?foo=bar&php=nut&jose=zap'; $this->assertEquals($expected, $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllLogTest.php0000644000000000000000000000217513365153155021637 0ustar rootrootaddTestDirectory(CORE_TEST_CASES . DS . 'Log'); $suite->addTestDirectory(CORE_TEST_CASES . DS . 'Log' . DS . 'Engine'); return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllControllerTest.php0000644000000000000000000000274713365153155023246 0ustar rootrootaddTestFile(CORE_TEST_CASES . DS . 'Controller' . DS . 'ControllerTest.php'); $suite->addTestFile(CORE_TEST_CASES . DS . 'Controller' . DS . 'ScaffoldTest.php'); $suite->addTestFile(CORE_TEST_CASES . DS . 'Controller' . DS . 'PagesControllerTest.php'); $suite->addTestFile(CORE_TEST_CASES . DS . 'Controller' . DS . 'ComponentTest.php'); $suite->addTestFile(CORE_TEST_CASES . DS . 'Controller' . DS . 'ControllerMergeVarsTest.php'); return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllNetworkTest.php0000644000000000000000000000234513365153155022546 0ustar rootrootaddTestDirectory(CORE_TEST_CASES . DS . 'Network'); $suite->addTestDirectory(CORE_TEST_CASES . DS . 'Network' . DS . 'Email'); $suite->addTestDirectory(CORE_TEST_CASES . DS . 'Network' . DS . 'Http'); return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/0000755000000000000000000000000013365153155021040 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/Fixture/0000755000000000000000000000000013365153155022466 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/Fixture/CakeFixtureManagerTest.php0000644000000000000000000000546413365153155027555 0ustar rootrootfixtureManager = new CakeFixtureManager(); } /** * tearDown * * @return void */ public function tearDown() { parent::tearDown(); unset($this->fixtureManager); } /** * testLoadTruncatesTable * * @return void */ public function testLoadTruncatesTable() { $MockFixture = $this->getMock('UuidFixture', array('truncate')); $MockFixture ->expects($this->once()) ->method('truncate') ->will($this->returnValue(true)); $fixtureManager = $this->fixtureManager; $fixtureManagerReflection = new ReflectionClass($fixtureManager); $loadedProperty = $fixtureManagerReflection->getProperty('_loaded'); $loadedProperty->setAccessible(true); $loadedProperty->setValue($fixtureManager, array('core.uuid' => $MockFixture)); $TestCase = $this->getMock('CakeTestCase'); $TestCase->fixtures = array('core.uuid'); $TestCase->autoFixtures = true; $TestCase->dropTables = false; $fixtureManager->load($TestCase); } /** * testLoadSingleTruncatesTable * * @return void */ public function testLoadSingleTruncatesTable() { $MockFixture = $this->getMock('UuidFixture', array('truncate')); $MockFixture ->expects($this->once()) ->method('truncate') ->will($this->returnValue(true)); $fixtureManager = $this->fixtureManager; $fixtureManagerReflection = new ReflectionClass($fixtureManager); $fixtureMapProperty = $fixtureManagerReflection->getProperty('_fixtureMap'); $fixtureMapProperty->setAccessible(true); $fixtureMapProperty->setValue($fixtureManager, array('UuidFixture' => $MockFixture)); $dboMethods = array_diff(get_class_methods('DboSource'), array('enabled')); $dboMethods[] = 'connect'; $db = $this->getMock('DboSource', $dboMethods); $db->config['prefix'] = ''; $fixtureManager->loadSingle('Uuid', $db, false); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/Stub/0000755000000000000000000000000013365153155021755 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/Stub/ConsoleOutputStubTest.php0000644000000000000000000000277013365153155027015 0ustar rootrootstub = new ConsoleOutputStub(); } /** * Test that stub can be used as an instance of ConsoleOutput * * @return void */ public function testCanActAsConsoleOutput() { $this->assertInstanceOf("ConsoleOutput", $this->stub); } /** * Test write method * * @return void */ public function testWrite() { $this->stub->write(array("foo", "bar", "baz")); $this->assertEquals(array("foo", "bar", "baz"), $this->stub->messages()); } /** * Test overwrite method * * @return void */ public function testOverwrite() { $this->stub->write(array("foo", "bar", "baz")); $this->stub->overwrite("bat"); $this->assertEquals(array("foo", "bar", "baz", "", "bat"), $this->stub->messages()); } }ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/ControllerTestCaseTest.php0000644000000000000000000005174013365153155026177 0ustar rootroot array( 'className' => 'Email', ), 'AliasedPluginEmail' => array( 'className' => 'TestPlugin.TestPluginEmail', ), 'Auth' ); } } /** * ControllerTestCaseTest controller * * @package Cake.Test.Case.TestSuite */ class ControllerTestCaseTestController extends AppController { /** * Uses array * * @param array */ public $uses = array('TestPlugin.TestPluginComment'); } /** * ControllerTestCaseTest * * @package Cake.Test.Case.TestSuite */ class ControllerTestCaseTest extends CakeTestCase { /** * fixtures property * * @var array */ public $fixtures = array('core.post', 'core.author', 'core.test_plugin_comment'); /** * reset environment. * * @return void */ public function setUp() { parent::setUp(); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'Controller' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Controller' . DS), 'Model' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS), 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin', 'TestPluginTwo')); $this->Case = $this->getMockForAbstractClass('ControllerTestCase'); Router::reload(); } /** * tearDown * * @return void */ public function tearDown() { parent::tearDown(); CakePlugin::unload(); $this->Case->controller = null; } /** * Test that ControllerTestCase::generate() creates mock objects correctly * * @return void */ public function testGenerate() { if (defined('APP_CONTROLLER_EXISTS')) { $this->markTestSkipped('AppController exists, cannot run.'); } $Posts = $this->Case->generate('Posts'); $this->assertEquals('Posts', $Posts->name); $this->assertEquals('Post', $Posts->modelClass); $this->assertNull($Posts->response->send()); $Posts = $this->Case->generate('Posts', array( 'methods' => array( 'render' ) )); $this->assertNull($Posts->render('index')); $Posts = $this->Case->generate('Posts', array( 'models' => array('Post'), 'components' => array('RequestHandler') )); $this->assertInstanceOf('Post', $Posts->Post); $this->assertNull($Posts->Post->save(array())); $this->assertNull($Posts->Post->find('all')); $this->assertEquals('posts', $Posts->Post->useTable); $this->assertNull($Posts->RequestHandler->isAjax()); $Posts = $this->Case->generate('Posts', array( 'models' => array( 'Post' => true ) )); $this->assertNull($Posts->Post->save(array())); $this->assertNull($Posts->Post->find('all')); $Posts = $this->Case->generate('Posts', array( 'models' => array( 'Post' => array('save'), ) )); $this->assertNull($Posts->Post->save(array())); $this->assertInternalType('array', $Posts->Post->find('all')); $Posts = $this->Case->generate('Posts', array( 'models' => array('Post'), 'components' => array( 'RequestHandler' => array('isPut'), 'Email' => array('send'), 'Session' ) )); $Posts->RequestHandler->expects($this->once()) ->method('isPut') ->will($this->returnValue(true)); $this->assertTrue($Posts->RequestHandler->isPut()); $Posts->Auth->Session->expects($this->any()) ->method('write') ->will($this->returnValue('written!')); $this->assertEquals('written!', $Posts->Auth->Session->write('something')); } /** * testGenerateWithComponentConfig * * @return void */ public function testGenerateWithComponentConfig() { $Tests = $this->Case->generate('TestConfigs', array( )); $expected = array('some' => 'config'); $settings = array_intersect_key($Tests->RequestHandler->settings, array('some' => 'foo')); $this->assertSame($expected, $settings, 'A mocked component should have the same config as an unmocked component'); $Tests = $this->Case->generate('TestConfigs', array( 'components' => array( 'RequestHandler' => array('isPut') ) )); $expected = array('some' => 'config'); $settings = array_intersect_key($Tests->RequestHandler->settings, array('some' => 'foo')); $this->assertSame($expected, $settings, 'A mocked component should have the same config as an unmocked component'); } /** * Tests ControllerTestCase::generate() using classes from plugins * * @return void */ public function testGenerateWithPlugin() { $Tests = $this->Case->generate('TestPlugin.Tests', array( 'models' => array( 'TestPlugin.TestPluginComment' ), 'components' => array( 'TestPlugin.Plugins' ) )); $this->assertEquals('Tests', $Tests->name); $this->assertInstanceOf('PluginsComponent', $Tests->Plugins); $result = ClassRegistry::init('TestPlugin.TestPluginComment'); $this->assertInstanceOf('TestPluginComment', $result); $Tests = $this->Case->generate('ControllerTestCaseTest', array( 'models' => array( 'TestPlugin.TestPluginComment' => array('save') ) )); $this->assertInstanceOf('TestPluginComment', $Tests->TestPluginComment); $Tests->TestPluginComment->expects($this->at(0)) ->method('save') ->will($this->returnValue(true)); $Tests->TestPluginComment->expects($this->at(1)) ->method('save') ->will($this->returnValue(false)); $this->assertTrue($Tests->TestPluginComment->save(array())); $this->assertFalse($Tests->TestPluginComment->save(array())); } /** * Tests ControllerTestCase::generate() using aliased component * * @return void */ public function testGenerateWithMockedAliasedComponent() { $Posts = $this->Case->generate('Posts', array( 'components' => array( 'AliasedEmail' => array('send') ) )); $Posts->AliasedEmail->expects($this->once()) ->method('send') ->will($this->returnValue(true)); $this->assertInstanceOf('EmailComponent', $Posts->AliasedEmail); $this->assertTrue($Posts->AliasedEmail->send()); } /** * Tests ControllerTestCase::generate() using aliased plugin component * * @return void */ public function testGenerateWithMockedAliasedPluginComponent() { $Posts = $this->Case->generate('Posts', array( 'components' => array( 'AliasedPluginEmail' => array('send') ) )); $Posts->AliasedPluginEmail->expects($this->once()) ->method('send') ->will($this->returnValue(true)); $this->assertInstanceOf('TestPluginEmailComponent', $Posts->AliasedPluginEmail); $this->assertTrue($Posts->AliasedPluginEmail->send()); } /** * Tests testAction * * @return void */ public function testTestAction() { $this->Case->generate('TestsApps'); $this->Case->testAction('/tests_apps/index'); $this->assertInternalType('array', $this->Case->controller->viewVars); $this->Case->testAction('/tests_apps/set_action'); $results = $this->Case->controller->viewVars; $expected = array( 'var' => 'string' ); $this->assertEquals($expected, $results); $result = $this->Case->controller->response->body(); $this->assertRegExp('/This is the TestsAppsController index view/', $result); $Controller = $this->Case->generate('TestsApps'); $this->Case->testAction('/tests_apps/redirect_to'); $results = $this->Case->headers; $expected = array( 'Location' => 'https://cakephp.org' ); $this->assertEquals($expected, $results); $this->assertSame(302, $Controller->response->statusCode()); } /** * Test array URLs with testAction() * * @return void */ public function testTestActionArrayUrls() { $this->Case->generate('TestsApps'); $this->Case->testAction(array('controller' => 'tests_apps', 'action' => 'index')); $this->assertInternalType('array', $this->Case->controller->viewVars); } /** * Test that file responses don't trigger errors. * * @return void */ public function testActionWithFile() { $Controller = $this->Case->generate('TestsApps'); $this->Case->testAction('/tests_apps/file'); $this->assertArrayHasKey('Content-Disposition', $Controller->response->header()); $this->assertArrayHasKey('Content-Length', $Controller->response->header()); } /** * Make sure testAction() can hit plugin controllers. * * @return void */ public function testTestActionWithPlugin() { $this->Case->generate('TestPlugin.Tests'); $this->Case->testAction('/test_plugin/tests/index'); $this->assertEquals('It is a variable', $this->Case->controller->viewVars['test_value']); } /** * Tests using loaded routes during tests * * @return void */ public function testUseRoutes() { Router::connect('/:controller/:action/*'); include CAKE . 'Test' . DS . 'test_app' . DS . 'Config' . DS . 'routes.php'; $controller = $this->Case->generate('TestsApps'); $controller->Components->load('RequestHandler'); $result = $this->Case->testAction('/tests_apps/index.json', array('return' => 'contents')); $result = json_decode($result, true); $expected = array('cakephp' => 'cool'); $this->assertEquals($expected, $result); include CAKE . 'Test' . DS . 'test_app' . DS . 'Config' . DS . 'routes.php'; $result = $this->Case->testAction('/some_alias'); $this->assertEquals(5, $result); } /** * Tests not using loaded routes during tests * * @expectedException MissingActionException * @return void */ public function testSkipRoutes() { Router::connect('/:controller/:action/*'); include CAKE . 'Test' . DS . 'test_app' . DS . 'Config' . DS . 'routes.php'; $this->Case->loadRoutes = false; $this->Case->testAction('/tests_apps/missing_action.json', array('return' => 'view')); } /** * Tests backwards compatibility with setting the return type * * @return void */ public function testBCSetReturn() { $this->Case->autoMock = true; $result = $this->Case->testAction('/tests_apps/some_method'); $this->assertEquals(5, $result); $data = array('var' => 'set'); $result = $this->Case->testAction('/tests_apps_posts/post_var', array( 'data' => $data, 'return' => 'vars' )); $this->assertEquals($data, $result['data']); $result = $this->Case->testAction('/tests_apps/set_action', array( 'return' => 'view' )); $this->assertEquals('This is the TestsAppsController index view string', $result); $result = $this->Case->testAction('/tests_apps/set_action', array( 'return' => 'contents' )); $this->assertRegExp('/assertRegExp('/This is the TestsAppsController index view/', $result); $this->assertRegExp('/<\/html>/', $result); } /** * Tests sending POST data to testAction * * @return void */ public function testTestActionPostData() { $this->Case->autoMock = true; $data = array( 'Post' => array( 'name' => 'Some Post' ) ); $this->Case->testAction('/tests_apps_posts/post_var', array( 'data' => $data )); $this->assertEquals($this->Case->controller->viewVars['data'], $data); $this->assertEquals($this->Case->controller->data, $data); $this->Case->testAction('/tests_apps_posts/post_var/named:param', array( 'data' => $data )); $expected = array( 'named' => 'param' ); $this->assertEquals($expected, $this->Case->controller->request->named); $this->assertEquals($this->Case->controller->data, $data); $result = $this->Case->testAction('/tests_apps_posts/post_var', array( 'return' => 'vars', 'method' => 'post', 'data' => array( 'name' => 'is jonas', 'pork' => 'and beans', ) )); $this->assertEquals(array('name', 'pork'), array_keys($result['data'])); $result = $this->Case->testAction('/tests_apps_posts/add', array('return' => 'vars')); $this->assertTrue(array_key_exists('posts', $result)); $this->assertEquals(4, count($result['posts'])); $this->assertTrue($this->Case->controller->request->is('post')); } /** * Tests sending GET data to testAction * * @return void */ public function testTestActionGetData() { $this->Case->autoMock = true; $this->Case->testAction('/tests_apps_posts/url_var', array( 'method' => 'get', 'data' => array( 'some' => 'var', 'lackof' => 'creativity' ) )); $this->assertEquals('var', $this->Case->controller->request->query['some']); $this->assertEquals('creativity', $this->Case->controller->request->query['lackof']); $result = $this->Case->testAction('/tests_apps_posts/url_var/var1:value1/var2:val2', array( 'return' => 'vars', 'method' => 'get', )); $this->assertEquals(array('var1', 'var2'), array_keys($result['params']['named'])); $result = $this->Case->testAction('/tests_apps_posts/url_var/gogo/val2', array( 'return' => 'vars', 'method' => 'get', )); $this->assertEquals(array('gogo', 'val2'), $result['params']['pass']); $this->Case->testAction('/tests_apps_posts/url_var', array( 'return' => 'vars', 'method' => 'get', 'data' => array( 'red' => 'health', 'blue' => 'mana' ) )); $query = $this->Case->controller->request->query; $this->assertTrue(isset($query['red'])); $this->assertTrue(isset($query['blue'])); } /** * Test that REST actions with XML/JSON input work. * * @return void */ public function testTestActionJsonData() { $result = $this->Case->testAction('/tests_apps_posts/input_data', array( 'return' => 'vars', 'method' => 'post', 'data' => '{"key":"value","json":true}' )); $this->assertEquals('value', $result['data']['key']); $this->assertTrue($result['data']['json']); } /** * Tests autoMock ability * * @return void */ public function testAutoMock() { $this->Case->autoMock = true; $this->Case->testAction('/tests_apps/set_action'); $results = $this->Case->controller->viewVars; $expected = array( 'var' => 'string' ); $this->assertEquals($expected, $results); } /** * Test using testAction and not mocking * * @return void */ public function testNoMocking() { $result = $this->Case->testAction('/tests_apps/some_method'); $this->Case->assertEquals(5, $result); $data = array('var' => 'set'); $result = $this->Case->testAction('/tests_apps_posts/post_var', array( 'data' => $data, 'return' => 'vars' )); $this->assertEquals($data, $result['data']); $result = $this->Case->testAction('/tests_apps/set_action', array( 'return' => 'view' )); $this->assertEquals('This is the TestsAppsController index view string', $result); $result = $this->Case->testAction('/tests_apps/set_action', array( 'return' => 'contents' )); $this->assertRegExp('/assertRegExp('/This is the TestsAppsController index view/', $result); $this->assertRegExp('/<\/html>/', $result); } /** * Test that controllers don't get reused. * * @return void */ public function testNoControllerReuse() { $this->Case->autoMock = true; $result = $this->Case->testAction('/tests_apps/index', array( 'data' => array('var' => 'first call'), 'method' => 'get', 'return' => 'contents', )); $this->assertContains('assertContains('This is the TestsAppsController index view', $result); $this->assertContains('first call', $result); $this->assertContains('', $result); $result = $this->Case->testAction('/tests_apps/index', array( 'data' => array('var' => 'second call'), 'method' => 'get', 'return' => 'contents' )); $this->assertContains('second call', $result); $result = $this->Case->testAction('/tests_apps/index', array( 'data' => array('var' => 'third call'), 'method' => 'get', 'return' => 'contents' )); $this->assertContains('third call', $result); } /** * Test that multiple calls to redirect in the same test method don't cause issues. * * @return void */ public function testTestActionWithMultipleRedirect() { $this->Case->generate('TestsApps'); $options = array('method' => 'get'); $this->Case->testAction('/tests_apps/redirect_to', $options); $this->Case->testAction('/tests_apps/redirect_to', $options); } /** * Tests that Components storing response or request objects internally during construct * will always have a fresh reference to those object available * * @return void */ public function testComponentsSameRequestAndResponse() { $this->Case->generate('TestsApps'); $options = array('method' => 'get'); $this->Case->testAction('/tests_apps/index', $options); $this->assertSame($this->Case->controller->response, $this->Case->controller->RequestHandler->response); $this->assertSame($this->Case->controller->request, $this->Case->controller->RequestHandler->request); } /** * Test that testAction() doesn't destroy data in GET & POST * * @return void */ public function testRestoreGetPost() { $restored = array('new' => 'value'); $_GET = $restored; $_POST = $restored; $this->Case->generate('TestsApps'); $options = array('method' => 'get'); $this->Case->testAction('/tests_apps/index', $options); $this->assertEquals($restored, $_GET); $this->assertEquals($restored, $_POST); } /** * Tests that the `App.base` path is properly stripped from the URL generated from the * given URL array, and that consequently the correct controller/action is being matched. * * @return void */ public function testAppBaseConfigCompatibilityWithArrayUrls() { Configure::write('App.base', '/cakephp'); $this->Case->generate('TestsApps'); $this->Case->testAction(array('controller' => 'tests_apps', 'action' => 'index')); $this->assertEquals('/cakephp', $this->Case->controller->request->base); $this->assertEquals('/cakephp/', $this->Case->controller->request->webroot); $this->assertEquals('/cakephp/tests_apps', $this->Case->controller->request->here); $this->assertEquals('tests_apps', $this->Case->controller->request->url); $expected = array( 'plugin' => null, 'controller' => 'tests_apps', 'action' => 'index', 'named' => array(), 'pass' => array(), ); $this->assertEquals($expected, array_intersect_key($this->Case->controller->request->params, $expected)); } /** * Tests that query string data from URL arrays properly makes it into the request object * on GET requests. * * @return void */ public function testTestActionWithArrayUrlQueryStringDataViaGetRequest() { $query = array('foo' => 'bar'); $this->Case->generate('TestsApps'); $this->Case->testAction( array( 'controller' => 'tests_apps', 'action' => 'index', '?' => $query ), array( 'method' => 'get' ) ); $this->assertEquals('tests_apps', $this->Case->controller->request->url); $this->assertEquals($query, $this->Case->controller->request->query); } /** * Tests that query string data from URL arrays properly makes it into the request object * on POST requests. * * @return void */ public function testTestActionWithArrayUrlQueryStringDataViaPostRequest() { $query = array('foo' => 'bar'); $this->Case->generate('TestsApps'); $this->Case->testAction( array( 'controller' => 'tests_apps', 'action' => 'index', '?' => $query ), array( 'method' => 'post' ) ); $this->assertEquals('tests_apps', $this->Case->controller->request->url); $this->assertEquals($query, $this->Case->controller->request->query); } /** * Tests that query string data from both, URL arrays as well as the `data` option, * properly makes it into the request object. * * @return void */ public function testTestActionWithArrayUrlQueryStringDataAndDataOptionViaGetRequest() { $query = array('foo' => 'bar'); $data = array('bar' => 'foo'); $this->Case->generate('TestsApps'); $this->Case->testAction( array( 'controller' => 'tests_apps', 'action' => 'index', '?' => $query ), array( 'method' => 'get', 'data' => $data ) ); $this->assertEquals('tests_apps', $this->Case->controller->request->url); $this->assertEquals($data + $query, $this->Case->controller->request->query); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/CakeTestFixtureTest.php0000644000000000000000000003634413365153155025475 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.TestSuite * @since CakePHP(tm) v 1.2.0.4667 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('DboSource', 'Model/Datasource'); App::uses('Model', 'Model'); App::uses('CakeTestFixture', 'TestSuite/Fixture'); /** * CakeTestFixtureTestFixture class * * @package Cake.Test.Case.TestSuite */ class CakeTestFixtureTestFixture extends CakeTestFixture { /** * Name property * * @var string */ public $name = 'FixtureTest'; /** * Table property * * @var string */ public $table = 'fixture_tests'; /** * Fields array * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => '255'), 'created' => array('type' => 'datetime') ); /** * Records property * * @var array */ public $records = array( array('name' => 'Gandalf', 'created' => '2009-04-28 19:20:00'), array('name' => 'Captain Picard', 'created' => '2009-04-28 19:20:00'), array('name' => 'Chewbacca', 'created' => '2009-04-28 19:20:00') ); } /** * StringTestFixture class * * @package Cake.Test.Case.TestSuite */ class StringsTestFixture extends CakeTestFixture { /** * Name property * * @var string */ public $name = 'Strings'; /** * Table property * * @var string */ public $table = 'strings'; /** * Fields array * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => '255'), 'email' => array('type' => 'string', 'length' => '255'), 'age' => array('type' => 'integer', 'default' => 10) ); /** * Records property * * @var array */ public $records = array( array('name' => 'Mark Doe', 'email' => 'mark.doe@email.com'), array('name' => 'John Doe', 'email' => 'john.doe@email.com', 'age' => 20), array('email' => 'jane.doe@email.com', 'name' => 'Jane Doe', 'age' => 30) ); } /** * InvalidTestFixture class * * @package Cake.Test.Case.TestSuite */ class InvalidTestFixture extends CakeTestFixture { /** * Name property * * @var string */ public $name = 'Invalid'; /** * Table property * * @var string */ public $table = 'invalid'; /** * Fields array - missing "email" row * * @var array */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => '255'), 'age' => array('type' => 'integer', 'default' => 10) ); /** * Records property * * @var array */ public $records = array( array('name' => 'Mark Doe', 'email' => 'mark.doe@email.com'), array('name' => 'John Doe', 'email' => 'john.doe@email.com', 'age' => 20), array('email' => 'jane.doe@email.com', 'name' => 'Jane Doe', 'age' => 30) ); } /** * CakeTestFixtureImportFixture class * * @package Cake.Test.Case.TestSuite */ class CakeTestFixtureImportFixture extends CakeTestFixture { /** * Name property * * @var string */ public $name = 'ImportFixture'; /** * Import property * * @var mixed */ public $import = array('table' => 'fixture_tests', 'connection' => 'fixture_test_suite'); } /** * CakeTestFixtureDefaultImportFixture class * * @package Cake.Test.Case.TestSuite */ class CakeTestFixtureDefaultImportFixture extends CakeTestFixture { /** * Name property * * @var string */ public $name = 'ImportFixture'; } /** * FixtureImportTestModel class * * @package Cake.Test.Case.TestSuite */ class FixtureImportTestModel extends Model { public $name = 'FixtureImport'; public $useTable = 'fixture_tests'; public $useDbConfig = 'test'; } class FixturePrefixTest extends Model { public $name = 'FixturePrefix'; public $useTable = '_tests'; public $tablePrefix = 'fixture'; public $useDbConfig = 'test'; } /** * Test case for CakeTestFixture * * @package Cake.Test.Case.TestSuite */ class CakeTestFixtureTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $methods = array_diff(get_class_methods('DboSource'), array('enabled')); $methods[] = 'connect'; $this->criticDb = $this->getMock('DboSource', $methods); $this->criticDb->fullDebug = true; $this->db = ConnectionManager::getDataSource('test'); $this->_backupConfig = $this->db->config; } /** * tearDown * * @return void */ public function tearDown() { parent::tearDown(); unset($this->criticDb); $this->db->config = $this->_backupConfig; } /** * testInit * * @return void */ public function testInit() { $Fixture = new CakeTestFixtureTestFixture(); unset($Fixture->table); $Fixture->init(); $this->assertEquals('fixture_tests', $Fixture->table); $this->assertEquals('id', $Fixture->primaryKey); $Fixture = new CakeTestFixtureTestFixture(); $Fixture->primaryKey = 'my_random_key'; $Fixture->init(); $this->assertEquals('my_random_key', $Fixture->primaryKey); } /** * test that init() correctly sets the fixture table when the connection * or model have prefixes defined. * * @return void */ public function testInitDbPrefix() { $this->skipIf($this->db instanceof Sqlite, 'Cannot open 2 connections to Sqlite'); $db = ConnectionManager::getDataSource('test'); $Source = new CakeTestFixtureTestFixture(); $Source->drop($db); $Source->create($db); $Source->insert($db); $Fixture = new CakeTestFixtureTestFixture(); $expected = array('id', 'name', 'created'); $this->assertEquals($expected, array_keys($Fixture->fields)); $config = $db->config; $config['prefix'] = 'fixture_test_suite_'; ConnectionManager::create('fixture_test_suite', $config); $Fixture->fields = $Fixture->records = null; $Fixture->import = array('table' => 'fixture_tests', 'connection' => 'test', 'records' => true); $Fixture->init(); $this->assertEquals(count($Fixture->records), count($Source->records)); $Fixture->create(ConnectionManager::getDataSource('fixture_test_suite')); $Fixture = new CakeTestFixtureImportFixture(); $Fixture->fields = $Fixture->records = $Fixture->table = null; $Fixture->import = array('model' => 'FixtureImportTestModel', 'connection' => 'test'); $Fixture->init(); $this->assertEquals(array('id', 'name', 'created'), array_keys($Fixture->fields)); $this->assertEquals('fixture_tests', $Fixture->table); $keys = array_flip(ClassRegistry::keys()); $this->assertFalse(array_key_exists('fixtureimporttestmodel', $keys)); $Fixture->drop(ConnectionManager::getDataSource('fixture_test_suite')); $Source->drop($db); } /** * test that fixtures don't duplicate the test db prefix. * * @return void */ public function testInitDbPrefixDuplication() { $this->skipIf($this->db instanceof Sqlite, 'Cannot open 2 connections to Sqlite'); $db = ConnectionManager::getDataSource('test'); $backPrefix = $db->config['prefix']; $db->config['prefix'] = 'cake_fixture_test_'; ConnectionManager::create('fixture_test_suite', $db->config); $newDb = ConnectionManager::getDataSource('fixture_test_suite'); $newDb->config['prefix'] = 'cake_fixture_test_'; $Source = new CakeTestFixtureTestFixture(); $Source->create($db); $Source->insert($db); $Fixture = new CakeTestFixtureImportFixture(); $Fixture->fields = $Fixture->records = $Fixture->table = null; $Fixture->import = array('model' => 'FixtureImportTestModel', 'connection' => 'test'); $Fixture->init(); $this->assertEquals(array('id', 'name', 'created'), array_keys($Fixture->fields)); $this->assertEquals('fixture_tests', $Fixture->table); $Source->drop($db); $db->config['prefix'] = $backPrefix; } /** * test init with a model that has a tablePrefix declared. * * @return void */ public function testInitModelTablePrefix() { $this->skipIf($this->db instanceof Sqlite, 'Cannot open 2 connections to Sqlite'); $this->skipIf(!empty($this->db->config['prefix']), 'Cannot run this test, you have a database connection prefix.'); $Source = new CakeTestFixtureTestFixture(); $Source->create($this->db); $Source->insert($this->db); $Fixture = new CakeTestFixtureTestFixture(); unset($Fixture->table); $Fixture->fields = $Fixture->records = null; $Fixture->import = array('model' => 'FixturePrefixTest', 'connection' => 'test', 'records' => false); $Fixture->init(); $this->assertEquals('fixture_tests', $Fixture->table); $keys = array_flip(ClassRegistry::keys()); $this->assertFalse(array_key_exists('fixtureimporttestmodel', $keys)); $Source->drop($this->db); } /** * testImport * * @return void */ public function testImport() { $testSuiteDb = ConnectionManager::getDataSource('test'); $testSuiteConfig = $testSuiteDb->config; ConnectionManager::create('new_test_suite', array_merge($testSuiteConfig, array('prefix' => 'new_' . $testSuiteConfig['prefix']))); $newTestSuiteDb = ConnectionManager::getDataSource('new_test_suite'); $Source = new CakeTestFixtureTestFixture(); $Source->create($newTestSuiteDb); $Source->insert($newTestSuiteDb); $Fixture = new CakeTestFixtureDefaultImportFixture(); $Fixture->fields = $Fixture->records = null; $Fixture->import = array('model' => 'FixtureImportTestModel', 'connection' => 'new_test_suite'); $Fixture->init(); $this->assertEquals(array('id', 'name', 'created'), array_keys($Fixture->fields)); $keys = array_flip(ClassRegistry::keys()); $this->assertFalse(array_key_exists('fixtureimporttestmodel', $keys)); $Source->drop($newTestSuiteDb); } /** * test that importing with records works. Make sure to try with postgres as its * handling of aliases is a workaround at best. * * @return void */ public function testImportWithRecords() { $testSuiteDb = ConnectionManager::getDataSource('test'); $testSuiteConfig = $testSuiteDb->config; ConnectionManager::create('new_test_suite', array_merge($testSuiteConfig, array('prefix' => 'new_' . $testSuiteConfig['prefix']))); $newTestSuiteDb = ConnectionManager::getDataSource('new_test_suite'); $Source = new CakeTestFixtureTestFixture(); $Source->create($newTestSuiteDb); $Source->insert($newTestSuiteDb); $Fixture = new CakeTestFixtureDefaultImportFixture(); $Fixture->fields = $Fixture->records = null; $Fixture->import = array( 'model' => 'FixtureImportTestModel', 'connection' => 'new_test_suite', 'records' => true ); $Fixture->init(); $this->assertEquals(array('id', 'name', 'created'), array_keys($Fixture->fields)); $this->assertFalse(empty($Fixture->records[0]), 'No records loaded on importing fixture.'); $this->assertTrue(isset($Fixture->records[0]['name']), 'No name loaded for first record'); $Source->drop($newTestSuiteDb); } /** * test create method * * @return void */ public function testCreate() { $Fixture = new CakeTestFixtureTestFixture(); $this->criticDb->expects($this->atLeastOnce())->method('execute'); $this->criticDb->expects($this->atLeastOnce())->method('createSchema'); $return = $Fixture->create($this->criticDb); $this->assertTrue($this->criticDb->fullDebug); $this->assertTrue($return); unset($Fixture->fields); $return = $Fixture->create($this->criticDb); $this->assertFalse($return); } /** * test the insert method * * @return void */ public function testInsert() { $Fixture = new CakeTestFixtureTestFixture(); $this->criticDb->expects($this->atLeastOnce()) ->method('insertMulti') ->will($this->returnCallback(array($this, 'insertCallback'))); $return = $Fixture->insert($this->criticDb); $this->assertTrue(!empty($this->insertMulti)); $this->assertTrue($this->criticDb->fullDebug); $this->assertTrue($return); $this->assertEquals('fixture_tests', $this->insertMulti['table']); $this->assertEquals(array('name', 'created'), $this->insertMulti['fields']); $expected = array( array('Gandalf', '2009-04-28 19:20:00'), array('Captain Picard', '2009-04-28 19:20:00'), array('Chewbacca', '2009-04-28 19:20:00') ); $this->assertEquals($expected, $this->insertMulti['values']); } /** * Helper function to be used as callback and store the parameters of an insertMulti call * * @param string $table * @param string $fields * @param string $values * @return bool true */ public function insertCallback($table, $fields, $values) { $this->insertMulti['table'] = $table; $this->insertMulti['fields'] = $fields; $this->insertMulti['values'] = $values; $this->insertMulti['fields_values'] = array(); foreach ($values as $record) { $this->insertMulti['fields_values'][] = array_combine($fields, $record); } return true; } /** * test the insert method * * @return void */ public function testInsertStrings() { $Fixture = new StringsTestFixture(); $this->criticDb->expects($this->atLeastOnce()) ->method('insertMulti') ->will($this->returnCallback(array($this, 'insertCallback'))); $return = $Fixture->insert($this->criticDb); $this->assertTrue($this->criticDb->fullDebug); $this->assertTrue($return); $this->assertEquals('strings', $this->insertMulti['table']); $this->assertEquals(array('name', 'email', 'age'), array_values($this->insertMulti['fields'])); $expected = array( array('Mark Doe', 'mark.doe@email.com', null), array('John Doe', 'john.doe@email.com', 20), array('Jane Doe', 'jane.doe@email.com', 30), ); $this->assertEquals($expected, $this->insertMulti['values']); $expected = array( array( 'name' => 'Mark Doe', 'email' => 'mark.doe@email.com', 'age' => null ), array( 'name' => 'John Doe', 'email' => 'john.doe@email.com', 'age' => 20 ), array( 'name' => 'Jane Doe', 'email' => 'jane.doe@email.com', 'age' => 30 ), ); $this->assertEquals($expected, $this->insertMulti['fields_values']); } /** * test the insert method with invalid fixture * * @expectedException CakeException * @return void */ public function testInsertInvalid() { $Fixture = new InvalidTestFixture(); $Fixture->insert($this->criticDb); } /** * Test the drop method * * @return void */ public function testDrop() { $Fixture = new CakeTestFixtureTestFixture(); $this->criticDb->expects($this->at(1))->method('execute')->will($this->returnValue(true)); $this->criticDb->expects($this->at(3))->method('execute')->will($this->returnValue(false)); $this->criticDb->expects($this->exactly(2))->method('dropSchema'); $return = $Fixture->drop($this->criticDb); $this->assertTrue($this->criticDb->fullDebug); $this->assertTrue($return); $return = $Fixture->drop($this->criticDb); $this->assertTrue($return); unset($Fixture->fields); $return = $Fixture->drop($this->criticDb); $this->assertFalse($return); } /** * Test the truncate method. * * @return void */ public function testTruncate() { $Fixture = new CakeTestFixtureTestFixture(); $this->criticDb->expects($this->atLeastOnce())->method('truncate'); $Fixture->truncate($this->criticDb); $this->assertTrue($this->criticDb->fullDebug); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/HtmlCoverageReportTest.php0000644000000000000000000001544413365153155026175 0ustar rootroot array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin')); $reporter = new CakeBaseReporter(); $reporter->params = array('app' => false, 'plugin' => false, 'group' => false); $coverage = array(); $this->Coverage = new HtmlCoverageReport($coverage, $reporter); } /** * test getting the path filters. * * @return void */ public function testGetPathFilter() { $this->Coverage->appTest = false; $result = $this->Coverage->getPathFilter(); $this->assertEquals(CAKE, $result); $this->Coverage->appTest = true; $result = $this->Coverage->getPathFilter(); $this->assertEquals(ROOT . DS . APP_DIR . DS, $result); $this->Coverage->appTest = false; $this->Coverage->pluginTest = 'TestPlugin'; $result = $this->Coverage->getPathFilter(); $this->assertEquals(CakePlugin::path('TestPlugin'), $result); } /** * test filtering coverage data. * * @return void */ public function testFilterCoverageDataByPathRemovingElements() { $data = array( CAKE . 'dispatcher.php' => array( 10 => -1, 12 => 1 ), APP . 'app_model.php' => array( 50 => 1, 52 => -1 ) ); $this->Coverage->setCoverage($data); $result = $this->Coverage->filterCoverageDataByPath(CAKE); $this->assertTrue(isset($result[CAKE . 'dispatcher.php'])); $this->assertFalse(isset($result[APP . 'app_model.php'])); } /** * test generating HTML reports from file arrays. * * @return void */ public function testGenerateDiff() { $file = array( 'line 1', 'line 2', 'line 3', 'line 4', 'line 5', 'line 6', 'line 7', 'line 8', 'line 9', 'line 10', ); $coverage = array( 1 => array(array('id' => 'HtmlCoverageReportTest::testGenerateDiff')), 2 => -2, 3 => array(array('id' => 'HtmlCoverageReportTest::testGenerateDiff')), 4 => array(array('id' => 'HtmlCoverageReportTest::testGenerateDiff')), 5 => -1, 6 => array(array('id' => 'HtmlCoverageReportTest::testGenerateDiff')), 7 => array(array('id' => 'HtmlCoverageReportTest::testGenerateDiff')), 8 => array(array('id' => 'HtmlCoverageReportTest::testGenerateDiff')), 9 => -1, 10 => array(array('id' => 'HtmlCoverageReportTest::testGenerateDiff')) ); $result = $this->Coverage->generateDiff('myfile.php', $file, $coverage); $this->assertRegExp('/myfile\.php Code coverage\: \d+\.?\d*\%/', $result); $this->assertRegExp('/
assertRegExp('/
/', $result);
		foreach ($file as $i => $line) {
			$this->assertTrue(strpos($line, $result) !== 0, 'Content is missing ' . $i);
			$class = 'covered';
			if (in_array($i + 1, array(5, 9, 2))) {
				$class = 'uncovered';
			}
			if ($i + 1 === 2) {
				$class .= ' dead';
			}
			$this->assertTrue(strpos($class, $result) !== 0, 'Class name is wrong ' . $i);
		}
	}

/**
 * Test that coverage works with phpunit 3.6 as the data formats from coverage are totally different.
 *
 * @return void
 */
	public function testPhpunit36Compatibility() {
		$file = array(
			'line 1',
			'line 2',
			'line 3',
			'line 4',
			'line 5',
			'line 6',
			'line 7',
			'line 8',
			'line 9',
			'line 10',
		);
		$coverage = array(
			1 => array('HtmlCoverageReportTest::testGenerateDiff'),
			2 => null,
			3 => array('HtmlCoverageReportTest::testGenerateDiff'),
			4 => array('HtmlCoverageReportTest::testGenerateDiff'),
			5 => array(),
			6 => array('HtmlCoverageReportTest::testGenerateDiff'),
			7 => array('HtmlCoverageReportTest::testGenerateDiff'),
			8 => array('HtmlCoverageReportTest::testGenerateDiff'),
			9 => array(),
			10 => array('HtmlCoverageReportTest::testSomething', 'HtmlCoverageReportTest::testGenerateDiff')
		);

		$result = $this->Coverage->generateDiff('myfile.php', $file, $coverage);
		$this->assertRegExp('/myfile\.php Code coverage\: \d+\.?\d*\%/', $result);
		$this->assertRegExp('/
assertRegExp('/
/', $result);
		foreach ($file as $i => $line) {
			$this->assertTrue(strpos($line, $result) !== 0, 'Content is missing ' . $i);
			$class = 'covered';
			if (in_array($i + 1, array(5, 9, 2))) {
				$class = 'uncovered';
			}
			if ($i + 1 === 2) {
				$class .= ' dead';
			}
			$this->assertTrue(strpos($class, $result) !== 0, 'Class name is wrong ' . $i);
		}
	}

/**
 * test that covering methods show up as title attributes for lines.
 *
 * @return void
 */
	public function testCoveredLinesTitleAttributes() {
		$file = array(
			'line 1',
			'line 2',
			'line 3',
			'line 4',
			'line 5',
		);

		$coverage = array(
			1 => array(array('id' => 'HtmlCoverageReportTest::testAwesomeness')),
			2 => -2,
			3 => array(array('id' => 'HtmlCoverageReportTest::testCakeIsSuperior')),
			4 => array(array('id' => 'HtmlCoverageReportTest::testOther')),
			5 => -1
		);

		$result = $this->Coverage->generateDiff('myfile.php', $file, $coverage);

		$this->assertTrue(
			strpos($result, "title=\"Covered by:\nHtmlCoverageReportTest::testAwesomeness\n\">1") !== false,
			'Missing method coverage for line 1'
		);
		$this->assertTrue(
			strpos($result, "title=\"Covered by:\nHtmlCoverageReportTest::testCakeIsSuperior\n\">3") !== false,
			'Missing method coverage for line 3'
		);
		$this->assertTrue(
			strpos($result, "title=\"Covered by:\nHtmlCoverageReportTest::testOther\n\">4") !== false,
			'Missing method coverage for line 4'
		);
		$this->assertTrue(
			strpos($result, "title=\"\">5") !== false,
			'Coverage report is wrong for line 5'
		);
	}

/**
 * tearDown
 *
 * @return void
 */
	public function tearDown() {
		CakePlugin::unload();
		unset($this->Coverage);
		parent::tearDown();
	}
}
ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php0000644000000000000000000003243013365153155024712 0ustar  rootrootgetDataSource()->cacheMethods = false;
	}

}

/**
 * CakeTestCaseTest
 *
 * @package       Cake.Test.Case.TestSuite
 */
class CakeTestCaseTest extends CakeTestCase {

/**
 * fixtures property
 *
 * @var array
 */
	public $fixtures = array('core.post', 'core.author', 'core.test_plugin_comment');

/**
 * CakeTestCaseTest::setUpBeforeClass()
 *
 * @return void
 */
	public static function setUpBeforeClass() {
		require_once CAKE . 'Test' . DS . 'Fixture' . DS . 'AssertTagsTestCase.php';
		require_once CAKE . 'Test' . DS . 'Fixture' . DS . 'FixturizedTestCase.php';
	}

/**
 * setUp
 *
 * @return void
 */
	public function setUp() {
		parent::setUp();
		$this->Reporter = $this->getMock('CakeHtmlReporter');
	}

/**
 * tearDown
 *
 * @return void
 */
	public function tearDown() {
		parent::tearDown();
		unset($this->Result);
		unset($this->Reporter);
	}

/**
 * testAssertTags
 *
 * @return void
 */
	public function testAssertTagsBasic() {
		$test = new AssertTagsTestCase('testAssertTagsQuotes');
		$result = $test->run();
		$this->assertEquals(0, $result->errorCount());
		$this->assertTrue($result->wasSuccessful());
		$this->assertEquals(0, $result->failureCount());
	}

/**
 * test assertTags works with single and double quotes
 *
 * @return void
 */
	public function testAssertTagsQuoting() {
		$input = 'My link';
		$pattern = array(
			'a' => array('href' => '/test.html', 'class' => 'active'),
			'My link',
			'/a'
		);
		$this->assertTags($input, $pattern);

		$input = "My link";
		$pattern = array(
			'a' => array('href' => '/test.html', 'class' => 'active'),
			'My link',
			'/a'
		);
		$this->assertTags($input, $pattern);

		$input = "My link";
		$pattern = array(
			'a' => array('href' => 'preg:/.*\.html/', 'class' => 'active'),
			'My link',
			'/a'
		);
		$this->assertTags($input, $pattern);

		$input = "Text";
		$pattern = array(
			'assertTags($input, $pattern);

		$input = "Text";
		$pattern = array(
			'span' => array('class'),
			'assertTags($input, $pattern);
	}

/**
 * Test that assertTags runs quickly.
 *
 * @return void
 */
	public function testAssertTagsRuntimeComplexity() {
		$pattern = array(
			'div' => array(
				'attr1' => 'val1',
				'attr2' => 'val2',
				'attr3' => 'val3',
				'attr4' => 'val4',
				'attr5' => 'val5',
				'attr6' => 'val6',
				'attr7' => 'val7',
				'attr8' => 'val8',
			),
			'My div',
			'/div'
		);
		$input = '
' . 'My div' . '
'; $this->assertTags($input, $pattern); } /** * testNumericValuesInExpectationForAssertTags * * @return void */ public function testNumericValuesInExpectationForAssertTags() { $test = new AssertTagsTestCase('testNumericValuesInExpectationForAssertTags'); $result = $test->run(); $this->assertEquals(0, $result->errorCount()); $this->assertTrue($result->wasSuccessful()); $this->assertEquals(0, $result->failureCount()); } /** * testBadAssertTags * * @return void */ public function testBadAssertTags() { $test = new AssertTagsTestCase('testBadAssertTags'); $result = $test->run(); $this->assertEquals(0, $result->errorCount()); $this->assertFalse($result->wasSuccessful()); $this->assertEquals(1, $result->failureCount()); $test = new AssertTagsTestCase('testBadAssertTags2'); $result = $test->run(); $this->assertEquals(0, $result->errorCount()); $this->assertFalse($result->wasSuccessful()); $this->assertEquals(1, $result->failureCount()); } /** * testLoadFixtures * * @return void */ public function testLoadFixtures() { $test = new FixturizedTestCase('testFixturePresent'); $manager = $this->getMock('CakeFixtureManager'); $manager->fixturize($test); $test->fixtureManager = $manager; $manager->expects($this->once())->method('load'); $manager->expects($this->once())->method('unload'); $result = $test->run(); $this->assertEquals(0, $result->errorCount()); $this->assertTrue($result->wasSuccessful()); $this->assertEquals(0, $result->failureCount()); } /** * testLoadFixturesOnDemand * * @return void */ public function testLoadFixturesOnDemand() { $test = new FixturizedTestCase('testFixtureLoadOnDemand'); $test->autoFixtures = false; $manager = $this->getMock('CakeFixtureManager'); $manager->fixturize($test); $test->fixtureManager = $manager; $manager->expects($this->once())->method('loadSingle'); $result = $test->run(); $this->assertEquals(0, $result->errorCount()); } /** * testLoadFixturesOnDemand * * @return void */ public function testUnoadFixturesAfterFailure() { $test = new FixturizedTestCase('testFixtureLoadOnDemand'); $test->autoFixtures = false; $manager = $this->getMock('CakeFixtureManager'); $manager->fixturize($test); $test->fixtureManager = $manager; $manager->expects($this->once())->method('loadSingle'); $result = $test->run(); $this->assertEquals(0, $result->errorCount()); } /** * testThrowException * * @return void */ public function testThrowException() { $test = new FixturizedTestCase('testThrowException'); $test->autoFixtures = false; $manager = $this->getMock('CakeFixtureManager'); $manager->fixturize($test); $test->fixtureManager = $manager; $manager->expects($this->once())->method('unload'); $result = $test->run(); $this->assertEquals(1, $result->errorCount()); } /** * testSkipIf * * @return void */ public function testSkipIf() { $test = new FixturizedTestCase('testSkipIfTrue'); $result = $test->run(); $this->assertEquals(1, $result->skippedCount()); $test = new FixturizedTestCase('testSkipIfFalse'); $result = $test->run(); $this->assertEquals(0, $result->skippedCount()); } /** * Test that CakeTestCase::setUp() backs up values. * * @return void */ public function testSetupBackUpValues() { $this->assertArrayHasKey('debug', $this->_configure); $this->assertArrayHasKey('Plugin', $this->_pathRestore); } /** * test assertTextNotEquals() * * @return void */ public function testAssertTextNotEquals() { $one = "\r\nOne\rTwooo"; $two = "\nOne\nTwo"; $this->assertTextNotEquals($one, $two); } /** * test assertTextEquals() * * @return void */ public function testAssertTextEquals() { $one = "\r\nOne\rTwo"; $two = "\nOne\nTwo"; $this->assertTextEquals($one, $two); } /** * test assertTextStartsWith() * * @return void */ public function testAssertTextStartsWith() { $stringDirty = "some\nstring\r\nwith\rdifferent\nline endings!"; $this->assertStringStartsWith("some\nstring", $stringDirty); $this->assertStringStartsNotWith("some\r\nstring\r\nwith", $stringDirty); $this->assertStringStartsNotWith("some\nstring\nwith", $stringDirty); $this->assertTextStartsWith("some\nstring\nwith", $stringDirty); $this->assertTextStartsWith("some\r\nstring\r\nwith", $stringDirty); } /** * test assertTextStartsNotWith() * * @return void */ public function testAssertTextStartsNotWith() { $stringDirty = "some\nstring\r\nwith\rdifferent\nline endings!"; $this->assertTextStartsNotWith("some\nstring\nwithout", $stringDirty); } /** * test assertTextEndsWith() * * @return void */ public function testAssertTextEndsWith() { $stringDirty = "some\nstring\r\nwith\rdifferent\nline endings!"; $this->assertTextEndsWith("string\nwith\r\ndifferent\rline endings!", $stringDirty); $this->assertTextEndsWith("string\r\nwith\ndifferent\nline endings!", $stringDirty); } /** * test assertTextEndsNotWith() * * @return void */ public function testAssertTextEndsNotWith() { $stringDirty = "some\nstring\r\nwith\rdifferent\nline endings!"; $this->assertStringEndsNotWith("different\nline endings", $stringDirty); $this->assertTextEndsNotWith("different\rline endings", $stringDirty); } /** * test assertTextContains() * * @return void */ public function testAssertTextContains() { $stringDirty = "some\nstring\r\nwith\rdifferent\nline endings!"; $this->assertContains("different", $stringDirty); $this->assertNotContains("different\rline", $stringDirty); $this->assertTextContains("different\rline", $stringDirty); } /** * test assertTextNotContains() * * @return void */ public function testAssertTextNotContains() { $stringDirty = "some\nstring\r\nwith\rdifferent\nline endings!"; $this->assertTextNotContains("different\rlines", $stringDirty); } /** * test getMockForModel() * * @return void */ public function testGetMockForModel() { App::build(array( 'Model' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS ) ), App::RESET); $Post = $this->getMockForModel('Post'); $this->assertEquals('test', $Post->useDbConfig); $this->assertInstanceOf('Post', $Post); $this->assertNull($Post->save(array())); $this->assertNull($Post->find('all')); $this->assertEquals('posts', $Post->useTable); $Post = $this->getMockForModel('Post', array('save')); $this->assertNull($Post->save(array())); $this->assertInternalType('array', $Post->find('all')); } /** * Test getMockForModel on secondary datasources. * * @return void */ public function testGetMockForModelSecondaryDatasource() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 'Model/Datasource/Database' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS . 'Datasource' . DS . 'Database' . DS ) ), App::RESET); CakePlugin::load('TestPlugin'); ConnectionManager::create('test_secondary', array( 'datasource' => 'Database/TestLocalDriver' )); $post = $this->getMockForModel('SecondaryPost', array('save')); $this->assertEquals('test_secondary', $post->useDbConfig); ConnectionManager::drop('test_secondary'); } /** * Test getMockForModel when the model accesses the datasource in the constructor. * * @return void */ public function testGetMockForModelConstructorDatasource() { $post = $this->getMockForModel('ConstructorPost', array('save'), array('ds' => 'test')); $this->assertEquals('test', $post->useDbConfig); } /** * test getMockForModel() with plugin models * * @return void */ public function testGetMockForModelWithPlugin() { App::build(array( 'Plugin' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS ) ), App::RESET); CakePlugin::load('TestPlugin'); $this->getMockForModel('TestPlugin.TestPluginAppModel'); $this->getMockForModel('TestPlugin.TestPluginComment'); $result = ClassRegistry::init('TestPlugin.TestPluginComment'); $this->assertInstanceOf('TestPluginComment', $result); $this->assertEquals('test', $result->useDbConfig); $TestPluginComment = $this->getMockForModel('TestPlugin.TestPluginComment', array('save')); $this->assertInstanceOf('TestPluginComment', $TestPluginComment); $TestPluginComment->expects($this->at(0)) ->method('save') ->will($this->returnValue(true)); $TestPluginComment->expects($this->at(1)) ->method('save') ->will($this->returnValue(false)); $this->assertTrue($TestPluginComment->save(array())); $this->assertFalse($TestPluginComment->save(array())); } /** * testGetMockForModelModel * * @return void */ public function testGetMockForModelModel() { $Mock = $this->getMockForModel('Model', array('save', 'setDataSource'), array('name' => 'Comment')); $result = ClassRegistry::init('Comment'); $this->assertInstanceOf('Model', $result); $Mock->expects($this->at(0)) ->method('save') ->will($this->returnValue(true)); $Mock->expects($this->at(1)) ->method('save') ->will($this->returnValue(false)); $this->assertTrue($Mock->save(array())); $this->assertFalse($Mock->save(array())); } /** * testGetMockForModelDoesNotExist * * @expectedException MissingModelException * @expectedExceptionMessage Model IDoNotExist could not be found * @return void */ public function testGetMockForModelDoesNotExist() { $this->getMockForModel('IDoNotExist'); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/TestSuite/CakeTestSuiteTest.php0000644000000000000000000000575413365153155025141 0ustar rootrootgetMock('CakeTestSuite', array('addTestFile')); $suite ->expects($this->exactly($count)) ->method('addTestFile'); $suite->addTestDirectory($testFolder); } /** * testAddTestDirectoryRecursive * * @return void */ public function testAddTestDirectoryRecursive() { $testFolder = CORE_TEST_CASES . DS . 'Cache'; $count = count(glob($testFolder . DS . '*Test.php')); $count += count(glob($testFolder . DS . 'Engine' . DS . '*Test.php')); $suite = $this->getMock('CakeTestSuite', array('addTestFile')); $suite ->expects($this->exactly($count)) ->method('addTestFile'); $suite->addTestDirectoryRecursive($testFolder); } /** * testAddTestDirectoryRecursiveWithHidden * * @return void */ public function testAddTestDirectoryRecursiveWithHidden() { $this->skipIf(!is_writable(TMP), 'Cant addTestDirectoryRecursiveWithHidden unless the tmp folder is writable.'); $Folder = new Folder(TMP . 'MyTestFolder', true, 0777); mkdir($Folder->path . DS . '.svn', 0777, true); touch($Folder->path . DS . '.svn' . DS . 'InHiddenFolderTest.php'); touch($Folder->path . DS . 'NotHiddenTest.php'); touch($Folder->path . DS . '.HiddenTest.php'); $suite = $this->getMock('CakeTestSuite', array('addTestFile')); $suite ->expects($this->exactly(1)) ->method('addTestFile'); $suite->addTestDirectoryRecursive($Folder->pwd()); $Folder->delete(); } /** * testAddTestDirectoryRecursiveWithNonPhp * * @return void */ public function testAddTestDirectoryRecursiveWithNonPhp() { $this->skipIf(!is_writable(TMP), 'Cant addTestDirectoryRecursiveWithNonPhp unless the tmp folder is writable.'); $Folder = new Folder(TMP . 'MyTestFolder', true, 0777); touch($Folder->path . DS . 'BackupTest.php~'); touch($Folder->path . DS . 'SomeNotesTest.txt'); touch($Folder->path . DS . 'NotHiddenTest.php'); $suite = $this->getMock('CakeTestSuite', array('addTestFile')); $suite ->expects($this->exactly(1)) ->method('addTestFile'); $suite->addTestDirectoryRecursive($Folder->pwd()); $Folder->delete(); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllComponentsTest.php0000644000000000000000000000245113365153155023240 0ustar rootrootaddTestFile(CORE_TEST_CASES . DS . 'Controller' . DS . 'ComponentTest.php'); $suite->addTestFile(CORE_TEST_CASES . DS . 'Controller' . DS . 'ComponentCollectionTest.php'); $suite->addTestDirectoryRecursive(CORE_TEST_CASES . DS . 'Controller' . DS . 'Component'); return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/BasicsTest.php0000644000000000000000000010573213365153155021674 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case * @since CakePHP(tm) v 1.2.0.4206 * @license https://opensource.org/licenses/mit-license.php MIT License */ require_once CAKE . 'basics.php'; App::uses('Folder', 'Utility'); App::uses('CakeResponse', 'Network'); App::uses('Debugger', 'Utility'); /** * BasicsTest class * * @package Cake.Test.Case */ class BasicsTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); App::build(array( 'Locale' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Locale' . DS) )); } /** * test the array_diff_key compatibility function. * * @return void */ public function testArrayDiffKey() { $one = array('one' => 1, 'two' => 2, 'three' => 3); $two = array('one' => 'one', 'two' => 'two'); $result = array_diff_key($one, $two); $expected = array('three' => 3); $this->assertEquals($expected, $result); $one = array('one' => array('value', 'value-two'), 'two' => 2, 'three' => 3); $two = array('two' => 'two'); $result = array_diff_key($one, $two); $expected = array('one' => array('value', 'value-two'), 'three' => 3); $this->assertEquals($expected, $result); $one = array('one' => null, 'two' => 2, 'three' => '', 'four' => 0); $two = array('two' => 'two'); $result = array_diff_key($one, $two); $expected = array('one' => null, 'three' => '', 'four' => 0); $this->assertEquals($expected, $result); $one = array('minYear' => null, 'maxYear' => null, 'separator' => '-', 'interval' => 1, 'monthNames' => true); $two = array('minYear' => null, 'maxYear' => null, 'separator' => '-', 'interval' => 1, 'monthNames' => true); $result = array_diff_key($one, $two); $this->assertSame(array(), $result); } /** * testHttpBase method * * @return void */ public function testEnv() { $this->skipIf(!function_exists('ini_get') || ini_get('safe_mode') === '1', 'Safe mode is on.'); $server = $_SERVER; $env = $_ENV; $_SERVER['HTTP_HOST'] = 'localhost'; $this->assertEquals(env('HTTP_BASE'), '.localhost'); $_SERVER['HTTP_HOST'] = 'com.ar'; $this->assertEquals(env('HTTP_BASE'), '.com.ar'); $_SERVER['HTTP_HOST'] = 'example.ar'; $this->assertEquals(env('HTTP_BASE'), '.example.ar'); $_SERVER['HTTP_HOST'] = 'example.com'; $this->assertEquals(env('HTTP_BASE'), '.example.com'); $_SERVER['HTTP_HOST'] = 'www.example.com'; $this->assertEquals(env('HTTP_BASE'), '.example.com'); $_SERVER['HTTP_HOST'] = 'subdomain.example.com'; $this->assertEquals(env('HTTP_BASE'), '.example.com'); $_SERVER['HTTP_HOST'] = 'example.com.ar'; $this->assertEquals(env('HTTP_BASE'), '.example.com.ar'); $_SERVER['HTTP_HOST'] = 'www.example.com.ar'; $this->assertEquals(env('HTTP_BASE'), '.example.com.ar'); $_SERVER['HTTP_HOST'] = 'subdomain.example.com.ar'; $this->assertEquals(env('HTTP_BASE'), '.example.com.ar'); $_SERVER['HTTP_HOST'] = 'double.subdomain.example.com'; $this->assertEquals(env('HTTP_BASE'), '.subdomain.example.com'); $_SERVER['HTTP_HOST'] = 'double.subdomain.example.com.ar'; $this->assertEquals(env('HTTP_BASE'), '.subdomain.example.com.ar'); $_SERVER = $_ENV = array(); $_SERVER['SCRIPT_NAME'] = '/a/test/test.php'; $this->assertEquals(env('SCRIPT_NAME'), '/a/test/test.php'); $_SERVER = $_ENV = array(); $_ENV['CGI_MODE'] = 'BINARY'; $_ENV['SCRIPT_URL'] = '/a/test/test.php'; $this->assertEquals(env('SCRIPT_NAME'), '/a/test/test.php'); $_SERVER = $_ENV = array(); $this->assertFalse(env('HTTPS')); $_SERVER['HTTPS'] = 'on'; $this->assertTrue(env('HTTPS')); $_SERVER['HTTPS'] = '1'; $this->assertTrue(env('HTTPS')); $_SERVER['HTTPS'] = 'I am not empty'; $this->assertTrue(env('HTTPS')); $_SERVER['HTTPS'] = 1; $this->assertTrue(env('HTTPS')); $_SERVER['HTTPS'] = 'off'; $this->assertFalse(env('HTTPS')); $_SERVER['HTTPS'] = false; $this->assertFalse(env('HTTPS')); $_SERVER['HTTPS'] = ''; $this->assertFalse(env('HTTPS')); $_SERVER = array(); $_ENV['SCRIPT_URI'] = 'https://domain.test/a/test.php'; $this->assertTrue(env('HTTPS')); $_ENV['SCRIPT_URI'] = 'http://domain.test/a/test.php'; $this->assertFalse(env('HTTPS')); $_SERVER = $_ENV = array(); $this->assertNull(env('TEST_ME')); $_ENV['TEST_ME'] = 'a'; $this->assertEquals(env('TEST_ME'), 'a'); $_SERVER['TEST_ME'] = 'b'; $this->assertEquals(env('TEST_ME'), 'b'); unset($_ENV['TEST_ME']); $this->assertEquals(env('TEST_ME'), 'b'); $_SERVER = $server; $_ENV = $env; } /** * Test h() * * @return void */ public function testH() { $string = ''; $result = h($string); $this->assertEquals('<foo>', $result); $in = array('this & that', '

Which one

'); $result = h($in); $expected = array('this & that', '<p>Which one</p>'); $this->assertEquals($expected, $result); $string = ' &  '; $result = h($string); $this->assertEquals('<foo> & &nbsp;', $result); $string = ' &  '; $result = h($string, false); $this->assertEquals('<foo> &  ', $result); $string = ' &  '; $result = h($string, 'UTF-8'); $this->assertEquals('<foo> & &nbsp;', $result); $arr = array('', ' '); $result = h($arr); $expected = array( '<foo>', '&nbsp;' ); $this->assertEquals($expected, $result); $arr = array('', ' '); $result = h($arr, false); $expected = array( '<foo>', ' ' ); $this->assertEquals($expected, $result); $arr = array('f' => '', 'n' => ' '); $result = h($arr, false); $expected = array( 'f' => '<foo>', 'n' => ' ' ); $this->assertEquals($expected, $result); // Test that boolean values are not converted to strings $result = h(false); $this->assertFalse($result); $arr = array('foo' => false, 'bar' => true); $result = h($arr); $this->assertFalse($result['foo']); $this->assertTrue($result['bar']); $obj = new stdClass(); $result = h($obj); $this->assertEquals('(object)stdClass', $result); $obj = new CakeResponse(array('body' => 'Body content')); $result = h($obj); $this->assertEquals('Body content', $result); } /** * Test am() * * @return void */ public function testAm() { $result = am(array('one', 'two'), 2, 3, 4); $expected = array('one', 'two', 2, 3, 4); $this->assertEquals($expected, $result); $result = am(array('one' => array(2, 3), 'two' => array('foo')), array('one' => array(4, 5))); $expected = array('one' => array(4, 5), 'two' => array('foo')); $this->assertEquals($expected, $result); } /** * test cache() * * @return void */ public function testCache() { $_cacheDisable = Configure::read('Cache.disable'); $this->skipIf($_cacheDisable, 'Cache is disabled, skipping cache() tests.'); Configure::write('Cache.disable', true); $result = cache('basics_test', 'simple cache write'); $this->assertNull($result); $result = cache('basics_test'); $this->assertNull($result); Configure::write('Cache.disable', false); $result = cache('basics_test', 'simple cache write'); $this->assertTrue((bool)$result); $this->assertTrue(file_exists(CACHE . 'basics_test')); $result = cache('basics_test'); $this->assertEquals('simple cache write', $result); if (file_exists(CACHE . 'basics_test')) { unlink(CACHE . 'basics_test'); } cache('basics_test', 'expired', '+1 second'); sleep(2); $result = cache('basics_test', null, '+1 second'); $this->assertNull($result); Configure::write('Cache.disable', $_cacheDisable); } /** * test clearCache() * * @return void */ public function testClearCache() { $cacheOff = Configure::read('Cache.disable'); $this->skipIf($cacheOff, 'Cache is disabled, skipping clearCache() tests.'); cache('views' . DS . 'basics_test.cache', 'simple cache write'); $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test.cache')); cache('views' . DS . 'basics_test_2.cache', 'simple cache write 2'); $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test_2.cache')); cache('views' . DS . 'basics_test_3.cache', 'simple cache write 3'); $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test_3.cache')); $result = clearCache(array('basics_test', 'basics_test_2'), 'views', '.cache'); $this->assertTrue($result); $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test.cache')); $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test.cache')); $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test_3.cache')); $result = clearCache(null, 'views', '.cache'); $this->assertTrue($result); $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test_3.cache')); // Different path from views and with prefix cache('models' . DS . 'basics_test.cache', 'simple cache write'); $this->assertTrue(file_exists(CACHE . 'models' . DS . 'basics_test.cache')); cache('models' . DS . 'basics_test_2.cache', 'simple cache write 2'); $this->assertTrue(file_exists(CACHE . 'models' . DS . 'basics_test_2.cache')); cache('models' . DS . 'basics_test_3.cache', 'simple cache write 3'); $this->assertTrue(file_exists(CACHE . 'models' . DS . 'basics_test_3.cache')); $result = clearCache('basics', 'models', '.cache'); $this->assertTrue($result); $this->assertFalse(file_exists(CACHE . 'models' . DS . 'basics_test.cache')); $this->assertFalse(file_exists(CACHE . 'models' . DS . 'basics_test_2.cache')); $this->assertFalse(file_exists(CACHE . 'models' . DS . 'basics_test_3.cache')); // checking if empty files were not removed $emptyExists = file_exists(CACHE . 'views' . DS . 'empty'); if (!$emptyExists) { cache('views' . DS . 'empty', ''); } cache('views' . DS . 'basics_test.php', 'simple cache write'); $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test.php')); $this->assertTrue(file_exists(CACHE . 'views' . DS . 'empty')); $result = clearCache(); $this->assertTrue($result); $this->assertTrue(file_exists(CACHE . 'views' . DS . 'empty')); $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test.php')); if (!$emptyExists) { unlink(CACHE . 'views' . DS . 'empty'); } } /** * test __() * * @return void */ public function testTranslate() { Configure::write('Config.language', 'rule_1_po'); $result = __('Plural Rule 1'); $expected = 'Plural Rule 1 (translated)'; $this->assertEquals($expected, $result); $result = __('Plural Rule 1 (from core)'); $expected = 'Plural Rule 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __('Some string with %s', 'arguments'); $expected = 'Some string with arguments'; $this->assertEquals($expected, $result); $result = __('Some string with %s %s', 'multiple', 'arguments'); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); $result = __('Some string with %s and a null argument', null); $expected = 'Some string with %s and a null argument'; $this->assertEquals($expected, $result); $result = __('Some string with multiple %s%s, first being null', null, 'arguments'); $expected = 'Some string with multiple arguments, first being null'; $this->assertEquals($expected, $result); $result = __('Some string with %s %s', array('multiple', 'arguments')); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); $result = __('Testing %2$s %1$s', 'order', 'different'); $expected = 'Testing different order'; $this->assertEquals($expected, $result); $result = __('Testing %2$s %1$s', array('order', 'different')); $expected = 'Testing different order'; $this->assertEquals($expected, $result); $result = __('Testing %.2f number', 1.2345); $expected = 'Testing 1.23 number'; $this->assertEquals($expected, $result); } /** * testTranslatePercent * * @return void */ public function testTranslatePercent() { $result = __('%s are 100% real fruit', 'Apples'); $expected = 'Apples are 100% real fruit'; $this->assertEquals($expected, $result, 'Percent sign at end of word should be considered literal'); $result = __('%s are %d% real fruit', 'Apples', 100); $expected = 'Apples are 100% real fruit'; $this->assertEquals($expected, $result, 'A digit marker should not be misinterpreted'); $result = __('%s are %s% real fruit', 'Apples', 100); $expected = 'Apples are 100% real fruit'; $this->assertEquals($expected, $result, 'A string marker should not be misinterpreted'); $result = __('%nonsense %s', 'Apples'); $expected = '%nonsense Apples'; $this->assertEquals($expected, $result, 'A percent sign at the start of the string should be considered literal'); $result = __('%s are awesome%', 'Apples'); $expected = 'Apples are awesome%'; $this->assertEquals($expected, $result, 'A percent sign at the end of the string should be considered literal'); $result = __('%2$d %1$s entered the bowl', 'Apples', 2); $expected = '2 Apples entered the bowl'; $this->assertEquals($expected, $result, 'Positional replacement markers should not be misinterpreted'); $result = __('%.2f% of all %s agree', 99.44444, 'Cats'); $expected = '99.44% of all Cats agree'; $this->assertEquals($expected, $result, 'significant-digit placeholder should not be misinterpreted'); } /** * testTranslateWithFormatSpecifiers * * @return void */ public function testTranslateWithFormatSpecifiers() { $expected = 'Check, one, two, three'; $result = __('Check, %+10s, three', 'one, two'); $this->assertEquals($expected, $result); $expected = 'Check, +1, two, three'; $result = __('Check, %+5d, two, three', 1); $this->assertEquals($expected, $result); $expected = 'Check, @@one, two, three'; $result = __('Check, %\'@+10s, three', 'one, two'); $this->assertEquals($expected, $result); $expected = 'Check, one, two , three'; $result = __('Check, %-10s, three', 'one, two'); $this->assertEquals($expected, $result); $expected = 'Check, one, two##, three'; $result = __('Check, %\'#-10s, three', 'one, two'); $this->assertEquals($expected, $result); $expected = 'Check, one, two, three'; $result = __d('default', 'Check, %+10s, three', 'one, two'); $this->assertEquals($expected, $result); $expected = 'Check, @@one, two, three'; $result = __d('default', 'Check, %\'@+10s, three', 'one, two'); $this->assertEquals($expected, $result); $expected = 'Check, one, two , three'; $result = __d('default', 'Check, %-10s, three', 'one, two'); $this->assertEquals($expected, $result); $expected = 'Check, one, two##, three'; $result = __d('default', 'Check, %\'#-10s, three', 'one, two'); $this->assertEquals($expected, $result); } /** * testTranslateDomainPluralWithFormatSpecifiers * * @return void */ public function testTranslateDomainPluralWithFormatSpecifiers() { $result = __dn('core', '%+5d item.', '%+5d items.', 1, 1); $expected = ' +1 item.'; $this->assertEquals($expected, $result); $result = __dn('core', '%-5d item.', '%-5d items.', 10, 10); $expected = '10 items.'; $this->assertEquals($expected, $result); $result = __dn('core', '%\'#+5d item.', '%\'*+5d items.', 1, 1); $expected = '###+1 item.'; $this->assertEquals($expected, $result); $result = __dn('core', '%\'#+5d item.', '%\'*+5d items.', 90, 90); $expected = '**+90 items.'; $this->assertEquals($expected, $result); $result = __dn('core', '%\'#+5d item.', '%\'*+5d items.', 9000, 9000); $expected = '+9000 items.'; $this->assertEquals($expected, $result); } /** * test testTranslatePluralWithFormatSpecifiers * * @return void */ public function testTranslatePluralWithFormatSpecifiers() { Configure::write('Config.language', 'rule_1_po'); $result = __n('%-5d = 1', '%-5d = 0 or > 1', 10); $expected = '%-5d = 0 or > 1 (translated)'; $this->assertEquals($expected, $result); } /** * test testTranslateDomainCategoryWithFormatSpecifiers * * @return void */ public function testTranslateDomainCategoryWithFormatSpecifiers() { Configure::write('Config.language', 'rule_1_po'); $result = __dc('default', '%+10s world', 6, 'hello'); $expected = ' hello world'; $this->assertEquals($expected, $result); $result = __dc('default', '%-10s world', 6, 'hello'); $expected = 'hello world'; $this->assertEquals($expected, $result); $result = __dc('default', '%\'@-10s world', 6, 'hello'); $expected = 'hello@@@@@ world'; $this->assertEquals($expected, $result); } /** * test testTranslateDomainCategoryPluralWithFormatSpecifiers * * @return void */ public function testTranslateDomainCategoryPluralWithFormatSpecifiers() { Configure::write('Config.language', 'rule_1_po'); $result = __dcn('default', '%-5d = 1', '%-5d = 0 or > 1', 0, 6); $expected = '%-5d = 0 or > 1 (translated)'; $this->assertEquals($expected, $result); $result = __dcn('default', '%-5d = 1', '%-5d = 0 or > 1', 1, 6); $expected = '%-5d = 1 (translated)'; $this->assertEquals($expected, $result); } /** * test testTranslateCategoryWithFormatSpecifiers * * @return void */ public function testTranslateCategoryWithFormatSpecifiers() { $result = __c('Some string with %+10s', 6, 'arguments'); $expected = 'Some string with arguments'; $this->assertEquals($expected, $result); $result = __c('Some string with %-10s: args', 6, 'arguments'); $expected = 'Some string with arguments : args'; $this->assertEquals($expected, $result); $result = __c('Some string with %\'*-10s: args', 6, 'arguments'); $expected = 'Some string with arguments*: args'; $this->assertEquals($expected, $result); } /** * test __n() * * @return void */ public function testTranslatePlural() { Configure::write('Config.language', 'rule_1_po'); $result = __n('%d = 1', '%d = 0 or > 1', 0); $expected = '%d = 0 or > 1 (translated)'; $this->assertEquals($expected, $result); $result = __n('%d = 1', '%d = 0 or > 1', 1); $expected = '%d = 1 (translated)'; $this->assertEquals($expected, $result); $result = __n('%d = 1 (from core)', '%d = 0 or > 1 (from core)', 2); $expected = '%d = 0 or > 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __n('%d item.', '%d items.', 1, 1); $expected = '1 item.'; $this->assertEquals($expected, $result); $result = __n('%d item for id %s', '%d items for id %s', 2, 2, '1234'); $expected = '2 items for id 1234'; $this->assertEquals($expected, $result); $result = __n('%d item for id %s', '%d items for id %s', 2, array(2, '1234')); $expected = '2 items for id 1234'; $this->assertEquals($expected, $result); } /** * test __d() * * @return void */ public function testTranslateDomain() { Configure::write('Config.language', 'rule_1_po'); $result = __d('default', 'Plural Rule 1'); $expected = 'Plural Rule 1 (translated)'; $this->assertEquals($expected, $result); $result = __d('core', 'Plural Rule 1'); $expected = 'Plural Rule 1'; $this->assertEquals($expected, $result); $result = __d('core', 'Plural Rule 1 (from core)'); $expected = 'Plural Rule 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __d('core', 'Some string with %s', 'arguments'); $expected = 'Some string with arguments'; $this->assertEquals($expected, $result); $result = __d('core', 'Some string with %s %s', 'multiple', 'arguments'); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); $result = __d('core', 'Some string with %s %s', array('multiple', 'arguments')); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); } /** * test __dn() * * @return void */ public function testTranslateDomainPlural() { Configure::write('Config.language', 'rule_1_po'); $result = __dn('default', '%d = 1', '%d = 0 or > 1', 0); $expected = '%d = 0 or > 1 (translated)'; $this->assertEquals($expected, $result); $result = __dn('core', '%d = 1', '%d = 0 or > 1', 0); $expected = '%d = 0 or > 1'; $this->assertEquals($expected, $result); $result = __dn('core', '%d = 1 (from core)', '%d = 0 or > 1 (from core)', 0); $expected = '%d = 0 or > 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __dn('default', '%d = 1', '%d = 0 or > 1', 1); $expected = '%d = 1 (translated)'; $this->assertEquals($expected, $result); $result = __dn('core', '%d item.', '%d items.', 1, 1); $expected = '1 item.'; $this->assertEquals($expected, $result); $result = __dn('core', '%d item for id %s', '%d items for id %s', 2, 2, '1234'); $expected = '2 items for id 1234'; $this->assertEquals($expected, $result); $result = __dn('core', '%d item for id %s', '%d items for id %s', 2, array(2, '1234')); $expected = '2 items for id 1234'; $this->assertEquals($expected, $result); } /** * test __c() * * @return void */ public function testTranslateCategory() { Configure::write('Config.language', 'rule_1_po'); $result = __c('Plural Rule 1', 6); $expected = 'Plural Rule 1 (translated)'; $this->assertEquals($expected, $result); $result = __c('Plural Rule 1 (from core)', 6); $expected = 'Plural Rule 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __c('Some string with %s', 6, 'arguments'); $expected = 'Some string with arguments'; $this->assertEquals($expected, $result); $result = __c('Some string with %s %s', 6, 'multiple', 'arguments'); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); $result = __c('Some string with %s %s', 6, array('multiple', 'arguments')); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); } /** * test __dc() * * @return void */ public function testTranslateDomainCategory() { Configure::write('Config.language', 'rule_1_po'); $result = __dc('default', 'Plural Rule 1', 6); $expected = 'Plural Rule 1 (translated)'; $this->assertEquals($expected, $result); $result = __dc('default', 'Plural Rule 1 (from core)', 6); $expected = 'Plural Rule 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __dc('core', 'Plural Rule 1', 6); $expected = 'Plural Rule 1'; $this->assertEquals($expected, $result); $result = __dc('core', 'Plural Rule 1 (from core)', 6); $expected = 'Plural Rule 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __dc('core', 'Some string with %s', 6, 'arguments'); $expected = 'Some string with arguments'; $this->assertEquals($expected, $result); $result = __dc('core', 'Some string with %s %s', 6, 'multiple', 'arguments'); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); $result = __dc('core', 'Some string with %s %s', 6, array('multiple', 'arguments')); $expected = 'Some string with multiple arguments'; $this->assertEquals($expected, $result); } /** * test __dcn() * * @return void */ public function testTranslateDomainCategoryPlural() { Configure::write('Config.language', 'rule_1_po'); $result = __dcn('default', '%d = 1', '%d = 0 or > 1', 0, 6); $expected = '%d = 0 or > 1 (translated)'; $this->assertEquals($expected, $result); $result = __dcn('default', '%d = 1 (from core)', '%d = 0 or > 1 (from core)', 1, 6); $expected = '%d = 1 (from core translated)'; $this->assertEquals($expected, $result); $result = __dcn('core', '%d = 1', '%d = 0 or > 1', 0, 6); $expected = '%d = 0 or > 1'; $this->assertEquals($expected, $result); $result = __dcn('core', '%d item.', '%d items.', 1, 6, 1); $expected = '1 item.'; $this->assertEquals($expected, $result); $result = __dcn('core', '%d item for id %s', '%d items for id %s', 2, 6, 2, '1234'); $expected = '2 items for id 1234'; $this->assertEquals($expected, $result); $result = __dcn('core', '%d item for id %s', '%d items for id %s', 2, 6, array(2, '1234')); $expected = '2 items for id 1234'; $this->assertEquals($expected, $result); } /** * test LogError() * * @return void */ public function testLogError() { if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } // disable stderr output for this test if (CakeLog::stream('stderr')) { CakeLog::disable('stderr'); } LogError('Testing LogError() basic function'); LogError("Testing with\nmulti-line\nstring"); if (CakeLog::stream('stderr')) { CakeLog::enable('stderr'); } $result = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/Error: Testing LogError\(\) basic function/', $result); $this->assertNotRegExp("/Error: Testing with\nmulti-line\nstring/", $result); $this->assertRegExp('/Error: Testing with multi-line string/', $result); } /** * test fileExistsInPath() * * @return void */ public function testFileExistsInPath() { if (!function_exists('ini_set')) { $this->markTestSkipped('%s ini_set function not available'); } $_includePath = ini_get('include_path'); $path = TMP . 'basics_test'; $folder1 = $path . DS . 'folder1'; $folder2 = $path . DS . 'folder2'; $file1 = $path . DS . 'file1.php'; $file2 = $folder1 . DS . 'file2.php'; $file3 = $folder1 . DS . 'file3.php'; $file4 = $folder2 . DS . 'file4.php'; new Folder($path, true); new Folder($folder1, true); new Folder($folder2, true); touch($file1); touch($file2); touch($file3); touch($file4); ini_set('include_path', $path . PATH_SEPARATOR . $folder1); $this->assertEquals(fileExistsInPath('file1.php'), $file1); $this->assertEquals(fileExistsInPath('file2.php'), $file2); $this->assertEquals(fileExistsInPath('folder1' . DS . 'file2.php'), $file2); $this->assertEquals(fileExistsInPath($file2), $file2); $this->assertEquals(fileExistsInPath('file3.php'), $file3); $this->assertEquals(fileExistsInPath($file4), $file4); $this->assertFalse(fileExistsInPath('file1')); $this->assertFalse(fileExistsInPath('file4.php')); $Folder = new Folder($path); $Folder->delete(); ini_set('include_path', $_includePath); } /** * test convertSlash() * * @return void */ public function testConvertSlash() { $result = convertSlash('\path\to\location\\'); $expected = '\path\to\location\\'; $this->assertEquals($expected, $result); $result = convertSlash('/path/to/location/'); $expected = 'path_to_location'; $this->assertEquals($expected, $result); } /** * test debug() * * @return void */ public function testDebug() { ob_start(); debug('this-is-a-test', false); $result = ob_get_clean(); $expectedText = <<assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', true); $result = ob_get_clean(); $expectedHtml = << %s (line %d)
'<div>this-is-a-test</div>'
EXPECTED; $expected = sprintf($expectedHtml, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 10); $this->assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', true, true); $result = ob_get_clean(); $expected = << %s (line %d)
'<div>this-is-a-test</div>'
EXPECTED; $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 10); $this->assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', true, false); $result = ob_get_clean(); $expected = <<
'<div>this-is-a-test</div>'
EXPECTED; $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 10); $this->assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', null); $result = ob_get_clean(); $expectedHtml = << %s (line %d)
'<div>this-is-a-test</div>'
EXPECTED; $expectedText = <<this-is-a-test' ########################### EXPECTED; if (PHP_SAPI === 'cli') { $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 18); } else { $expected = sprintf($expectedHtml, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 20); } $this->assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', null, false); $result = ob_get_clean(); $expectedHtml = <<
'<div>this-is-a-test</div>'
EXPECTED; $expectedText = <<this-is-a-test' ########################### EXPECTED; if (PHP_SAPI === 'cli') { $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 18); } else { $expected = sprintf($expectedHtml, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 19); } $this->assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', false); $result = ob_get_clean(); $expected = <<this-is-a-test' ########################### EXPECTED; $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', false, true); $result = ob_get_clean(); $expected = <<this-is-a-test' ########################### EXPECTED; $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); ob_start(); debug('
this-is-a-test
', false, false); $result = ob_get_clean(); $expected = <<this-is-a-test' ########################### EXPECTED; $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); ob_start(); debug(false, false, false); $result = ob_get_clean(); $expected = <<assertEquals($expected, $result); } /** * test pr() * * @return void */ public function testPr() { $this->skipIf(PHP_SAPI === 'cli', 'Skipping web test in cli mode'); ob_start(); pr('this is a test'); $result = ob_get_clean(); $expected = "
this is a test
"; $this->assertEquals($expected, $result); ob_start(); pr(array('this' => 'is', 'a' => 'test')); $result = ob_get_clean(); $expected = "
Array\n(\n    [this] => is\n    [a] => test\n)\n
"; $this->assertEquals($expected, $result); } /** * test pr() * * @return void */ public function testPrCli() { $this->skipIf(PHP_SAPI !== 'cli', 'Skipping cli test in web mode'); ob_start(); pr('this is a test'); $result = ob_get_clean(); $expected = "\nthis is a test\n"; $this->assertEquals($expected, $result); ob_start(); pr(array('this' => 'is', 'a' => 'test')); $result = ob_get_clean(); $expected = "\nArray\n(\n [this] => is\n [a] => test\n)\n\n"; $this->assertEquals($expected, $result); } /** * test stripslashes_deep() * * @return void */ public function testStripslashesDeep() { $this->skipIf(ini_get('magic_quotes_sybase') === '1', 'magic_quotes_sybase is on.'); $this->assertEquals(stripslashes_deep("tes\'t"), "tes't"); $this->assertEquals(stripslashes_deep('tes\\' . chr(0) . 't'), 'tes' . chr(0) . 't'); $this->assertEquals(stripslashes_deep('tes\"t'), 'tes"t'); $this->assertEquals(stripslashes_deep("tes\'t"), "tes't"); $this->assertEquals(stripslashes_deep('te\\st'), 'test'); $nested = array( 'a' => "tes\'t", 'b' => 'tes\\' . chr(0) . 't', 'c' => array( 'd' => 'tes\"t', 'e' => "te\'s\'t", array('f' => "tes\'t") ), 'g' => 'te\\st' ); $expected = array( 'a' => "tes't", 'b' => 'tes' . chr(0) . 't', 'c' => array( 'd' => 'tes"t', 'e' => "te's't", array('f' => "tes't") ), 'g' => 'test' ); $this->assertEquals($expected, stripslashes_deep($nested)); } /** * test stripslashes_deep() with magic_quotes_sybase on * * @return void */ public function testStripslashesDeepSybase() { if (!(ini_get('magic_quotes_sybase') === '1')) { $this->markTestSkipped('magic_quotes_sybase is off'); } $this->assertEquals(stripslashes_deep("tes\'t"), "tes\'t"); $nested = array( 'a' => "tes't", 'b' => "tes''t", 'c' => array( 'd' => "tes'''t", 'e' => "tes''''t", array('f' => "tes''t") ), 'g' => "te'''''st" ); $expected = array( 'a' => "tes't", 'b' => "tes't", 'c' => array( 'd' => "tes''t", 'e' => "tes''t", array('f' => "tes't") ), 'g' => "te'''st" ); $this->assertEquals($expected, stripslashes_deep($nested)); } /** * Tests that the stackTrace() method is a shortcut for Debugger::trace() * * @return void */ public function testStackTrace() { ob_start(); list(, $expected) = array(stackTrace(), Debugger::trace()); $result = ob_get_clean(); $this->assertEquals($expected, $result); $opts = array('args' => true); ob_start(); list(, $expected) = array(stackTrace($opts), Debugger::trace($opts)); $result = ob_get_clean(); $this->assertEquals($expected, $result); } /** * test pluginSplit * * @return void */ public function testPluginSplit() { $result = pluginSplit('Something.else'); $this->assertEquals(array('Something', 'else'), $result); $result = pluginSplit('Something.else.more.dots'); $this->assertEquals(array('Something', 'else.more.dots'), $result); $result = pluginSplit('Somethingelse'); $this->assertEquals(array(null, 'Somethingelse'), $result); $result = pluginSplit('Something.else', true); $this->assertEquals(array('Something.', 'else'), $result); $result = pluginSplit('Something.else.more.dots', true); $this->assertEquals(array('Something.', 'else.more.dots'), $result); $result = pluginSplit('Post', false, 'Blog'); $this->assertEquals(array('Blog', 'Post'), $result); $result = pluginSplit('Blog.Post', false, 'Ultimate'); $this->assertEquals(array('Blog', 'Post'), $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Error/0000755000000000000000000000000013365153155020200 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Error/ErrorHandlerTest.php0000644000000000000000000002340113365153155024140 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.Error * @since CakePHP(tm) v 1.2.0.5432 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('ErrorHandler', 'Error'); App::uses('Controller', 'Controller'); App::uses('Router', 'Routing'); App::uses('Debugger', 'Utility'); /** * A faulty ExceptionRenderer to test nesting. */ class FaultyExceptionRenderer extends ExceptionRenderer { /** * Dummy rendering implementation. * * @return void * @throws Exception */ public function render() { throw new Exception('Error from renderer.'); } } /** * ErrorHandlerTest class * * @package Cake.Test.Case.Error */ class ErrorHandlerTest extends CakeTestCase { protected $_restoreError = false; /** * setup create a request object to get out of router later. * * @return void */ public function setUp() { parent::setUp(); App::build(array( 'View' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS ) ), App::RESET); Router::reload(); $request = new CakeRequest(null, false); $request->base = ''; Router::setRequestInfo($request); Configure::write('debug', 2); CakeLog::disable('stdout'); CakeLog::disable('stderr'); } /** * tearDown * * @return void */ public function tearDown() { parent::tearDown(); if ($this->_restoreError) { restore_error_handler(); } CakeLog::enable('stdout'); CakeLog::enable('stderr'); } /** * test error handling when debug is on, an error should be printed from Debugger. * * @return void */ public function testHandleErrorDebugOn() { set_error_handler('ErrorHandler::handleError'); $this->_restoreError = true; Debugger::getInstance()->output('html'); ob_start(); $wrong .= ''; $result = ob_get_clean(); $this->assertRegExp('/
/', $result);
		$this->assertRegExp('/Notice<\/b>/', $result);
		$this->assertRegExp('/variable:\s+wrong/', $result);
	}

/**
 * provides errors for mapping tests.
 *
 * @return void
 */
	public static function errorProvider() {
		return array(
			array(E_USER_NOTICE, 'Notice'),
			array(E_USER_WARNING, 'Warning'),
		);
	}

/**
 * test error mappings
 *
 * @dataProvider errorProvider
 * @return void
 */
	public function testErrorMapping($error, $expected) {
		set_error_handler('ErrorHandler::handleError');
		$this->_restoreError = true;

		Debugger::getInstance()->output('html');

		ob_start();
		trigger_error('Test error', $error);

		$result = ob_get_clean();
		$this->assertRegExp('/' . $expected . '<\/b>/', $result);
	}

/**
 * test error prepended by @
 *
 * @return void
 */
	public function testErrorSuppressed() {
		set_error_handler('ErrorHandler::handleError');
		$this->_restoreError = true;

		ob_start();
		//@codingStandardsIgnoreStart
		@include 'invalid.file';
		//@codingStandardsIgnoreEnd
		$result = ob_get_clean();
		$this->assertTrue(empty($result));
	}

/**
 * Test that errors go into CakeLog when debug = 0.
 *
 * @return void
 */
	public function testHandleErrorDebugOff() {
		Configure::write('debug', 0);
		Configure::write('Error.trace', false);
		if (file_exists(LOGS . 'debug.log')) {
			unlink(LOGS . 'debug.log');
		}

		set_error_handler('ErrorHandler::handleError');
		$this->_restoreError = true;

		$out .= '';

		$result = file(LOGS . 'debug.log');
		$this->assertEquals(1, count($result));
		$this->assertRegExp(
			'/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} (Notice|Debug): Notice \(8\): Undefined variable:\s+out in \[.+ line \d+\]$/',
			$result[0]
		);
		if (file_exists(LOGS . 'debug.log')) {
			unlink(LOGS . 'debug.log');
		}
	}

/**
 * Test that errors going into CakeLog include traces.
 *
 * @return void
 */
	public function testHandleErrorLoggingTrace() {
		Configure::write('debug', 0);
		Configure::write('Error.trace', true);
		if (file_exists(LOGS . 'debug.log')) {
			unlink(LOGS . 'debug.log');
		}

		set_error_handler('ErrorHandler::handleError');
		$this->_restoreError = true;

		$out .= '';

		$result = file(LOGS . 'debug.log');
		$this->assertRegExp(
			'/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} (Notice|Debug): Notice \(8\): Undefined variable:\s+out in \[.+ line \d+\]$/',
			$result[0]
		);
		$this->assertRegExp('/^Trace:/', $result[1]);
		$this->assertRegExp('/^ErrorHandlerTest\:\:testHandleErrorLoggingTrace\(\)/', $result[3]);
		if (file_exists(LOGS . 'debug.log')) {
			unlink(LOGS . 'debug.log');
		}
	}

/**
 * test handleException generating a page.
 *
 * @return void
 */
	public function testHandleException() {
		$error = new NotFoundException('Kaboom!');
		ob_start();
		ErrorHandler::handleException($error);
		$result = ob_get_clean();
		$this->assertRegExp('/Kaboom!/', $result, 'message missing.');
	}

/**
 * test handleException generating log.
 *
 * @return void
 */
	public function testHandleExceptionLog() {
		if (file_exists(LOGS . 'error.log')) {
			unlink(LOGS . 'error.log');
		}
		Configure::write('Exception.log', true);
		$error = new NotFoundException('Kaboom!');

		ob_start();
		ErrorHandler::handleException($error);
		$result = ob_get_clean();
		$this->assertRegExp('/Kaboom!/', $result, 'message missing.');

		$log = file(LOGS . 'error.log');
		$this->assertContains('[NotFoundException] Kaboom!', $log[0], 'message missing.');
		$this->assertContains('ErrorHandlerTest->testHandleExceptionLog', $log[2], 'Stack trace missing.');
	}

/**
 * test handleException generating log.
 *
 * @return void
 */
	public function testHandleExceptionLogSkipping() {
		if (file_exists(LOGS . 'error.log')) {
			unlink(LOGS . 'error.log');
		}
		Configure::write('Exception.log', true);
		Configure::write('Exception.skipLog', array('NotFoundException'));
		$notFound = new NotFoundException('Kaboom!');
		$forbidden = new ForbiddenException('Fooled you!');

		ob_start();
		ErrorHandler::handleException($notFound);
		$result = ob_get_clean();
		$this->assertRegExp('/Kaboom!/', $result, 'message missing.');

		ob_start();
		ErrorHandler::handleException($forbidden);
		$result = ob_get_clean();
		$this->assertRegExp('/Fooled you!/', $result, 'message missing.');

		$log = file(LOGS . 'error.log');
		$this->assertNotContains('[NotFoundException] Kaboom!', $log[0], 'message should not be logged.');
		$this->assertContains('[ForbiddenException] Fooled you!', $log[0], 'message missing.');
	}

/**
 * tests it is possible to load a plugin exception renderer
 *
 * @return void
 */
	public function testLoadPluginHandler() {
		App::build(array(
			'Plugin' => array(
				CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS
			)
		), App::RESET);
		CakePlugin::load('TestPlugin');
		Configure::write('Exception.renderer', 'TestPlugin.TestPluginExceptionRenderer');
		$error = new NotFoundException('Kaboom!');
		ob_start();
		ErrorHandler::handleException($error);
		$result = ob_get_clean();
		$this->assertEquals('Rendered by test plugin', $result);
		CakePlugin::unload();
	}

/**
 * test handleFatalError generating a page.
 *
 * These tests start two buffers as handleFatalError blows the outer one up.
 *
 * @return void
 */
	public function testHandleFatalErrorPage() {
		$line = __LINE__;

		ob_start();
		ob_start();
		Configure::write('debug', 1);
		ErrorHandler::handleFatalError(E_ERROR, 'Something wrong', __FILE__, $line);
		$result = ob_get_clean();
		$this->assertContains('Something wrong', $result, 'message missing.');
		$this->assertContains(__FILE__, $result, 'filename missing.');
		$this->assertContains((string)$line, $result, 'line missing.');

		ob_start();
		ob_start();
		Configure::write('debug', 0);
		ErrorHandler::handleFatalError(E_ERROR, 'Something wrong', __FILE__, $line);
		$result = ob_get_clean();
		$this->assertNotContains('Something wrong', $result, 'message must not appear.');
		$this->assertNotContains(__FILE__, $result, 'filename must not appear.');
		$this->assertContains('An Internal Error Has Occurred', $result);
	}

/**
 * test handleException generating log.
 *
 * @return void
 */
	public function testHandleFatalErrorLog() {
		if (file_exists(LOGS . 'error.log')) {
			unlink(LOGS . 'error.log');
		}

		ob_start();
		ErrorHandler::handleFatalError(E_ERROR, 'Something wrong', __FILE__, __LINE__);
		ob_clean();

		$log = file(LOGS . 'error.log');
		$this->assertContains(__FILE__, $log[0], 'missing filename');
		$this->assertContains('[FatalErrorException] Something wrong', $log[1], 'message missing.');
	}

/**
 * testExceptionRendererNestingDebug method
 *
 * @return void
 */
	public function testExceptionRendererNestingDebug() {
		Configure::write('debug', 2);
		Configure::write('Exception.renderer', 'FaultyExceptionRenderer');

		$result = false;
		try {
			ob_start();
			ob_start();
			ErrorHandler::handleFatalError(E_USER_ERROR, 'Initial error', __FILE__, __LINE__);
		} catch (Exception $e) {
			$result = $e instanceof FatalErrorException;
		}

		restore_error_handler();
		$this->assertTrue($result);
	}

/**
 * testExceptionRendererNestingProduction method
 *
 * @return void
 */
	public function testExceptionRendererNestingProduction() {
		Configure::write('debug', 0);
		Configure::write('Exception.renderer', 'FaultyExceptionRenderer');

		$result = false;
		try {
			ob_start();
			ob_start();
			ErrorHandler::handleFatalError(E_USER_ERROR, 'Initial error', __FILE__, __LINE__);
		} catch (Exception $e) {
			$result = $e instanceof InternalErrorException;
		}

		restore_error_handler();
		$this->assertTrue($result);
	}

}
ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Error/ExceptionRendererTest.php0000644000000000000000000006344213365153155025207 0ustar  rootroot
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
 * @package       Cake.Test.Case.Error
 * @since         CakePHP(tm) v 2.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */

App::uses('ExceptionRenderer', 'Error');
App::uses('Controller', 'Controller');
App::uses('Component', 'Controller');
App::uses('Router', 'Routing');
App::uses('CakeEventManager', 'Event');

/**
 * Short description for class.
 *
 * @package       Cake.Test.Case.Error
 */
class AuthBlueberryUser extends CakeTestModel {

/**
 * useTable property
 *
 * @var string
 */
	public $useTable = false;
}

/**
 * BlueberryComponent class
 *
 * @package       Cake.Test.Case.Error
 */
class BlueberryComponent extends Component {

/**
 * testName property
 *
 * @return void
 */
	public $testName = null;

/**
 * initialize method
 *
 * @return void
 */
	public function initialize(Controller $controller) {
		$this->testName = 'BlueberryComponent';
	}

}

/**
 * TestErrorController class
 *
 * @package       Cake.Test.Case.Error
 */
class TestErrorController extends Controller {

/**
 * uses property
 *
 * @var array
 */
	public $uses = array();

/**
 * components property
 *
 * @return void
 */
	public $components = array('Blueberry');

/**
 * beforeRender method
 *
 * @return void
 */
	public function beforeRender() {
		echo $this->Blueberry->testName;
	}

/**
 * index method
 *
 * @return void
 */
	public function index() {
		$this->autoRender = false;
		return 'what up';
	}

}

/**
 * MyCustomExceptionRenderer class
 *
 * @package       Cake.Test.Case.Error
 */
class MyCustomExceptionRenderer extends ExceptionRenderer {

/**
 * custom error message type.
 *
 * @return void
 */
	public function missingWidgetThing() {
		echo 'widget thing is missing';
	}

}

/**
 * Exception class for testing app error handlers and custom errors.
 *
 * @package       Cake.Test.Case.Error
 */
class MissingWidgetThingException extends NotFoundException {
}

/**
 * ExceptionRendererTest class
 *
 * @package       Cake.Test.Case.Error
 */
class ExceptionRendererTest extends CakeTestCase {

	protected $_restoreError = false;

/**
 * setup create a request object to get out of router later.
 *
 * @return void
 */
	public function setUp() {
		parent::setUp();
		Configure::write('Config.language', 'eng');
		App::build(array(
			'View' => array(
				CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS
			)
		), App::RESET);
		Router::reload();

		$request = new CakeRequest(null, false);
		$request->base = '';
		Router::setRequestInfo($request);
		Configure::write('debug', 2);
	}

/**
 * tearDown
 *
 * @return void
 */
	public function tearDown() {
		parent::tearDown();
		if ($this->_restoreError) {
			restore_error_handler();
		}
	}

/**
 * Mocks out the response on the ExceptionRenderer object so headers aren't modified.
 *
 * @return void
 */
	protected function _mockResponse($error) {
		$error->controller->response = $this->getMock('CakeResponse', array('_sendHeader'));
		return $error;
	}

/**
 * test that methods declared in an ExceptionRenderer subclass are not converted
 * into error400 when debug > 0
 *
 * @return void
 */
	public function testSubclassMethodsNotBeingConvertedToError() {
		Configure::write('debug', 2);

		$exception = new MissingWidgetThingException('Widget not found');
		$ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertEquals('widget thing is missing', $result);
	}

/**
 * test that subclass methods are not converted when debug = 0
 *
 * @return void
 */
	public function testSubclassMethodsNotBeingConvertedDebug0() {
		Configure::write('debug', 0);
		$exception = new MissingWidgetThingException('Widget not found');
		$ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));

		$this->assertEquals('missingWidgetThing', $ExceptionRenderer->method);

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertEquals('widget thing is missing', $result, 'Method declared in subclass converted to error400');
	}

/**
 * test that ExceptionRenderer subclasses properly convert framework errors.
 *
 * @return void
 */
	public function testSubclassConvertingFrameworkErrors() {
		Configure::write('debug', 0);

		$exception = new MissingControllerException('PostsController');
		$ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));

		$this->assertEquals('error400', $ExceptionRenderer->method);

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertRegExp('/Not Found/', $result, 'Method declared in error handler not converted to error400. %s');
	}

/**
 * test things in the constructor.
 *
 * @return void
 */
	public function testConstruction() {
		$exception = new NotFoundException('Page not found');
		$ExceptionRenderer = new ExceptionRenderer($exception);

		$this->assertInstanceOf('CakeErrorController', $ExceptionRenderer->controller);
		$this->assertEquals('error400', $ExceptionRenderer->method);
		$this->assertEquals($exception, $ExceptionRenderer->error);
	}

/**
 * test that method gets coerced when debug = 0
 *
 * @return void
 */
	public function testErrorMethodCoercion() {
		Configure::write('debug', 0);
		$exception = new MissingActionException('Page not found');
		$ExceptionRenderer = new ExceptionRenderer($exception);

		$this->assertInstanceOf('CakeErrorController', $ExceptionRenderer->controller);
		$this->assertEquals('error400', $ExceptionRenderer->method);
		$this->assertEquals($exception, $ExceptionRenderer->error);
	}

/**
 * test that helpers in custom CakeErrorController are not lost
 *
 * @return void
 */
	public function testCakeErrorHelpersNotLost() {
		$testApp = CAKE . 'Test' . DS . 'test_app' . DS;
		App::build(array(
			'Controller' => array(
				$testApp . 'Controller' . DS
			),
			'View/Helper' => array(
				$testApp . 'View' . DS . 'Helper' . DS
			),
			'View/Layouts' => array(
				$testApp . 'View' . DS . 'Layouts' . DS
			),
			'Error' => array(
				$testApp . 'Error' . DS
			),
		), App::RESET);

		App::uses('TestAppsExceptionRenderer', 'Error');
		$exception = new SocketException('socket exception');
		$renderer = new TestAppsExceptionRenderer($exception);

		ob_start();
		$renderer->render();
		$result = ob_get_clean();
		$this->assertContains('peeled', $result);
	}

/**
 * test that unknown exception types with valid status codes are treated correctly.
 *
 * @return void
 */
	public function testUnknownExceptionTypeWithExceptionThatHasA400Code() {
		$exception = new MissingWidgetThingException('coding fail.');
		$ExceptionRenderer = new ExceptionRenderer($exception);
		$ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
		$ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertFalse(method_exists($ExceptionRenderer, 'missingWidgetThing'), 'no method should exist.');
		$this->assertEquals('error400', $ExceptionRenderer->method, 'incorrect method coercion.');
		$this->assertContains('coding fail', $result, 'Text should show up.');
	}

/**
 * test that unknown exception types with valid status codes are treated correctly.
 *
 * @return void
 */
	public function testUnknownExceptionTypeWithNoCodeIsA500() {
		$exception = new OutOfBoundsException('foul ball.');
		$ExceptionRenderer = new ExceptionRenderer($exception);
		$ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
		$ExceptionRenderer->controller->response->expects($this->once())
			->method('statusCode')
			->with(500);

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertEquals('error500', $ExceptionRenderer->method, 'incorrect method coercion.');
		$this->assertContains('foul ball.', $result, 'Text should show up as its debug mode.');
	}

/**
 * test that unknown exceptions have messages ignored.
 *
 * @return void
 */
	public function testUnknownExceptionInProduction() {
		Configure::write('debug', 0);

		$exception = new OutOfBoundsException('foul ball.');
		$ExceptionRenderer = new ExceptionRenderer($exception);
		$ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
		$ExceptionRenderer->controller->response->expects($this->once())
			->method('statusCode')
			->with(500);

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertEquals('error500', $ExceptionRenderer->method, 'incorrect method coercion.');
		$this->assertNotContains('foul ball.', $result, 'Text should no show up.');
		$this->assertContains('Internal Error', $result, 'Generic message only.');
	}

/**
 * test that unknown exception types with valid status codes are treated correctly.
 *
 * @return void
 */
	public function testUnknownExceptionTypeWithCodeHigherThan500() {
		$exception = new OutOfBoundsException('foul ball.', 501);
		$ExceptionRenderer = new ExceptionRenderer($exception);
		$ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
		$ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(501);

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertEquals('error500', $ExceptionRenderer->method, 'incorrect method coercion.');
		$this->assertContains('foul ball.', $result, 'Text should show up as its debug mode.');
	}

/**
 * testerror400 method
 *
 * @return void
 */
	public function testError400() {
		Router::reload();

		$request = new CakeRequest('posts/view/1000', false);
		Router::setRequestInfo($request);

		$exception = new NotFoundException('Custom message');
		$ExceptionRenderer = new ExceptionRenderer($exception);
		$ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
		$ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);

		ob_start();
		$ExceptionRenderer->render();
		$result = ob_get_clean();

		$this->assertRegExp('/

Custom message<\/h2>/', $result); $this->assertRegExp("/'.*?\/posts\/view\/1000'<\/strong>/", $result); } /** * test that error400 only modifies the messages on CakeExceptions. * * @return void */ public function testerror400OnlyChangingCakeException() { Configure::write('debug', 0); $exception = new NotFoundException('Custom message'); $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception)); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); $this->assertContains('Custom message', $result); $exception = new MissingActionException(array('controller' => 'PostsController', 'action' => 'index')); $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception)); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); $this->assertContains('Not Found', $result); } /** * test that error400 doesn't expose XSS * * @return void */ public function testError400NoInjection() { Router::reload(); $request = new CakeRequest('pages/pink', false); Router::setRequestInfo($request); $exception = new NotFoundException('Custom message'); $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception)); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); $this->assertNotRegExp('##', $result); } /** * testError500 method * * @return void */ public function testError500Message() { $exception = new InternalErrorException('An Internal Error Has Occurred'); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader')); $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); $this->assertRegExp('/

An Internal Error Has Occurred<\/h2>/', $result); } /** * testExceptionResponseHeader method * * @return void */ public function testExceptionResponseHeader() { $exception = new MethodNotAllowedException('Only allowing POST and DELETE'); $exception->responseHeader(array('Allow: POST, DELETE')); $ExceptionRenderer = new ExceptionRenderer($exception); //Replace response object with mocked object add back the original headers which had been set in ExceptionRenderer constructor $headers = $ExceptionRenderer->controller->response->header(); $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('_sendHeader')); $ExceptionRenderer->controller->response->header($headers); $ExceptionRenderer->controller->response->expects($this->at(1))->method('_sendHeader')->with('Allow', 'POST, DELETE'); ob_start(); $ExceptionRenderer->render(); ob_get_clean(); } /** * testMissingController method * * @return void */ public function testMissingController() { $exception = new MissingControllerException(array('class' => 'PostsController')); $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception)); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); $this->assertRegExp('/

Missing Controller<\/h2>/', $result); $this->assertRegExp('/PostsController<\/em>/', $result); } /** * Returns an array of tests to run for the various CakeException classes. * * @return void */ public static function testProvider() { return array( array( new MissingActionException(array('controller' => 'PostsController', 'action' => 'index')), array( '/

Missing Method in PostsController<\/h2>/', '/PostsController::<\/em>index\(\)<\/em>/' ), 404 ), array( new PrivateActionException(array('controller' => 'PostsController', 'action' => '_secretSauce')), array( '/

Private Method in PostsController<\/h2>/', '/PostsController::<\/em>_secretSauce\(\)<\/em>/' ), 404 ), array( new MissingTableException(array('table' => 'articles', 'class' => 'Article', 'ds' => 'test')), array( '/

Missing Database Table<\/h2>/', '/Table articles<\/em> for model Article<\/em> was not found in datasource test<\/em>/' ), 500 ), array( new MissingDatabaseException(array('connection' => 'default')), array( '/

Missing Database Connection<\/h2>/', '/Confirm you have created the file/' ), 500 ), array( new MissingViewException(array('file' => '/posts/about.ctp')), array( "/posts\/about.ctp/" ), 500 ), array( new MissingLayoutException(array('file' => 'layouts/my_layout.ctp')), array( "/Missing Layout/", "/layouts\/my_layout.ctp/" ), 500 ), array( new MissingConnectionException(array('class' => 'Mysql')), array( '/

Missing Database Connection<\/h2>/', '/A Database connection using "Mysql" was missing or unable to connect./', ), 500 ), array( new MissingConnectionException(array('class' => 'Mysql', 'enabled' => false)), array( '/

Missing Database Connection<\/h2>/', '/A Database connection using "Mysql" was missing or unable to connect./', '/Mysql driver is NOT enabled/' ), 500 ), array( new MissingDatasourceConfigException(array('config' => 'default')), array( '/

Missing Datasource Configuration<\/h2>/', '/The datasource configuration default<\/em> was not found in database.php/' ), 500 ), array( new MissingDatasourceException(array('class' => 'MyDatasource', 'plugin' => 'MyPlugin')), array( '/

Missing Datasource<\/h2>/', '/Datasource class MyPlugin.MyDatasource<\/em> could not be found/' ), 500 ), array( new MissingHelperException(array('class' => 'MyCustomHelper')), array( '/

Missing Helper<\/h2>/', '/MyCustomHelper<\/em> could not be found./', '/Create the class MyCustomHelper<\/em> below in file:/', '/(\/|\\\)MyCustomHelper.php/' ), 500 ), array( new MissingBehaviorException(array('class' => 'MyCustomBehavior')), array( '/

Missing Behavior<\/h2>/', '/Create the class MyCustomBehavior<\/em> below in file:/', '/(\/|\\\)MyCustomBehavior.php/' ), 500 ), array( new MissingComponentException(array('class' => 'SideboxComponent')), array( '/

Missing Component<\/h2>/', '/Create the class SideboxComponent<\/em> below in file:/', '/(\/|\\\)SideboxComponent.php/' ), 500 ), array( new Exception('boom'), array( '/Internal Error/' ), 500 ), array( new RuntimeException('another boom'), array( '/Internal Error/' ), 500 ), array( new CakeException('base class'), array('/Internal Error/'), 500 ), array( new ConfigureException('No file'), array('/Internal Error/'), 500 ) ); } /** * Test the various CakeException sub classes * * @dataProvider testProvider * @return void */ public function testCakeExceptionHandling($exception, $patterns, $code) { $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader')); $ExceptionRenderer->controller->response->expects($this->once()) ->method('statusCode') ->with($code); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); foreach ($patterns as $pattern) { $this->assertRegExp($pattern, $result); } } /** * Test exceptions being raised when helpers are missing. * * @return void */ public function testMissingRenderSafe() { $exception = new MissingHelperException(array('class' => 'Fail')); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller = $this->getMock('Controller', array('render')); $ExceptionRenderer->controller->helpers = array('Fail', 'Boom'); $ExceptionRenderer->controller->request = $this->getMock('CakeRequest'); $ExceptionRenderer->controller->expects($this->at(0)) ->method('render') ->with('missingHelper') ->will($this->throwException($exception)); $response = $this->getMock('CakeResponse'); $response->expects($this->once()) ->method('body') ->with($this->stringContains('Helper class Fail')); $ExceptionRenderer->controller->response = $response; $ExceptionRenderer->render(); sort($ExceptionRenderer->controller->helpers); $this->assertEquals(array('Form', 'Html', 'Session'), $ExceptionRenderer->controller->helpers); } /** * Test that exceptions in beforeRender() are handled by outputMessageSafe * * @return void */ public function testRenderExceptionInBeforeRender() { $exception = new NotFoundException('Not there, sorry'); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller = $this->getMock('Controller', array('beforeRender')); $ExceptionRenderer->controller->request = $this->getMock('CakeRequest'); $ExceptionRenderer->controller->expects($this->any()) ->method('beforeRender') ->will($this->throwException($exception)); $response = $this->getMock('CakeResponse'); $response->expects($this->once()) ->method('body') ->with($this->stringContains('Not there, sorry')); $ExceptionRenderer->controller->response = $response; $ExceptionRenderer->render(); } /** * Test that missing subDir/layoutPath don't cause other fatal errors. * * @return void */ public function testMissingSubdirRenderSafe() { $exception = new NotFoundException(); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller = $this->getMock('Controller', array('render')); $ExceptionRenderer->controller->helpers = array('Fail', 'Boom'); $ExceptionRenderer->controller->layoutPath = 'json'; $ExceptionRenderer->controller->subDir = 'json'; $ExceptionRenderer->controller->viewClass = 'Json'; $ExceptionRenderer->controller->request = $this->getMock('CakeRequest'); $ExceptionRenderer->controller->expects($this->once()) ->method('render') ->with('error400') ->will($this->throwException($exception)); $response = $this->getMock('CakeResponse'); $response->expects($this->once()) ->method('body') ->with($this->stringContains('Not Found')); $response->expects($this->once()) ->method('type') ->with('html'); $ExceptionRenderer->controller->response = $response; $ExceptionRenderer->render(); $this->assertEquals('', $ExceptionRenderer->controller->layoutPath); $this->assertEquals('', $ExceptionRenderer->controller->subDir); $this->assertEquals('Errors', $ExceptionRenderer->controller->viewPath); } /** * Test that missing plugin disables Controller::$plugin if the two are the same plugin. * * @return void */ public function testMissingPluginRenderSafe() { $exception = new NotFoundException(); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller = $this->getMock('Controller', array('render')); $ExceptionRenderer->controller->plugin = 'TestPlugin'; $ExceptionRenderer->controller->request = $this->getMock('CakeRequest'); $exception = new MissingPluginException(array('plugin' => 'TestPlugin')); $ExceptionRenderer->controller->expects($this->once()) ->method('render') ->with('error400') ->will($this->throwException($exception)); $response = $this->getMock('CakeResponse'); $response->expects($this->once()) ->method('body') ->with($this->logicalAnd( $this->logicalNot($this->stringContains('test plugin error500')), $this->stringContains('Not Found') )); $ExceptionRenderer->controller->response = $response; $ExceptionRenderer->render(); } /** * Test that missing plugin doesn't disable Controller::$plugin if the two aren't the same plugin. * * @return void */ public function testMissingPluginRenderSafeWithPlugin() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load('TestPlugin'); $exception = new NotFoundException(); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller = $this->getMock('Controller', array('render')); $ExceptionRenderer->controller->plugin = 'TestPlugin'; $ExceptionRenderer->controller->request = $this->getMock('CakeRequest'); $exception = new MissingPluginException(array('plugin' => 'TestPluginTwo')); $ExceptionRenderer->controller->expects($this->once()) ->method('render') ->with('error400') ->will($this->throwException($exception)); $response = $this->getMock('CakeResponse'); $response->expects($this->once()) ->method('body') ->with($this->logicalAnd( $this->stringContains('test plugin error500'), $this->stringContains('Not Found') )); $ExceptionRenderer->controller->response = $response; $ExceptionRenderer->render(); CakePlugin::unload(); } /** * Test that exceptions can be rendered when an request hasn't been registered * with Router * * @return void */ public function testRenderWithNoRequest() { Router::reload(); $this->assertNull(Router::getRequest(false)); $exception = new Exception('Terrible'); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader')); $ExceptionRenderer->controller->response->expects($this->once()) ->method('statusCode') ->with(500); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); $this->assertContains('Internal Error', $result); } /** * Tests the output of rendering a PDOException * * @return void */ public function testPDOException() { $exception = new PDOException('There was an error in the SQL query'); $exception->queryString = 'SELECT * from poo_query < 5 and :seven'; $exception->params = array('seven' => 7); $ExceptionRenderer = new ExceptionRenderer($exception); $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader')); $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500); ob_start(); $ExceptionRenderer->render(); $result = ob_get_clean(); $this->assertContains('

Database Error

', $result); $this->assertContains('There was an error in the SQL query', $result); $this->assertContains(h('SELECT * from poo_query < 5 and :seven'), $result); $this->assertContains("'seven' => (int) 7", $result); } /** * Test that rendering exceptions triggers shutdown events. * * @return void */ public function testRenderShutdownEvents() { $fired = array(); $listener = function ($event) use (&$fired) { $fired[] = $event->name(); }; $EventManager = CakeEventManager::instance(); $EventManager->attach($listener, 'Controller.shutdown'); $EventManager->attach($listener, 'Dispatcher.afterDispatch'); $exception = new Exception('Terrible'); $ExceptionRenderer = new ExceptionRenderer($exception); ob_start(); $ExceptionRenderer->render(); ob_get_clean(); $expected = array('Controller.shutdown', 'Dispatcher.afterDispatch'); $this->assertEquals($expected, $fired); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Event/0000755000000000000000000000000013365153155020170 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Event/CakeEventTest.php0000644000000000000000000000440113365153155023405 0ustar rootrootassertEquals('fake.event', $event->name()); } /** * Tests the subject() method * * @return void * @triggers fake.event $this * @triggers fake.event */ public function testSubject() { $event = new CakeEvent('fake.event', $this); $this->assertSame($this, $event->subject()); $event = new CakeEvent('fake.event'); $this->assertNull($event->subject()); } /** * Tests the event propagation stopping property * * @return void * @triggers fake.event */ public function testPropagation() { $event = new CakeEvent('fake.event'); $this->assertFalse($event->isStopped()); $event->stopPropagation(); $this->assertTrue($event->isStopped()); } /** * Tests that it is possible to get/set custom data in a event * * @return void * @triggers fake.event $this, array('some' => 'data') */ public function testEventData() { $event = new CakeEvent('fake.event', $this, array('some' => 'data')); $this->assertEquals(array('some' => 'data'), $event->data); } /** * Tests that it is possible to get the name and subject directly * * @return void * @triggers fake.event $this */ public function testEventDirectPropertyAccess() { $event = new CakeEvent('fake.event', $this); $this->assertEquals($this, $event->subject); $this->assertEquals('fake.event', $event->name); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/Event/CakeEventManagerTest.php0000644000000000000000000004121213365153155024701 0ustar rootrootcallStack[] = __FUNCTION__; } /** * Test function to be used in event dispatching * * @return void */ public function secondListenerFunction() { $this->callStack[] = __FUNCTION__; } /** * Auxiliary function to help in stopPropagation testing * * @param CakeEvent $event * @return void */ public function stopListener($event) { $event->stopPropagation(); } } /** * Mock used for testing the subscriber objects * * @package Cake.Test.Case.Event */ class CustomTestEventListener extends CakeEventTestListener implements CakeEventListener { public function implementedEvents() { return array( 'fake.event' => 'listenerFunction', 'another.event' => array('callable' => 'secondListenerFunction', 'passParams' => true), 'multiple.handlers' => array( array('callable' => 'listenerFunction'), array('callable' => 'thirdListenerFunction') ) ); } /** * Test function to be used in event dispatching * * @return void */ public function thirdListenerFunction() { $this->callStack[] = __FUNCTION__; } } /** * Tests the CakeEventManager class functionality */ class CakeEventManagerTest extends CakeTestCase { /** * Tests the attach() method for a single event key in multiple queues * * @return void */ public function testAttachListeners() { $manager = new CakeEventManager(); $manager->attach('fakeFunction', 'fake.event'); $expected = array( array('callable' => 'fakeFunction', 'passParams' => false) ); $this->assertEquals($expected, $manager->listeners('fake.event')); $manager->attach('fakeFunction2', 'fake.event'); $expected[] = array('callable' => 'fakeFunction2', 'passParams' => false); $this->assertEquals($expected, $manager->listeners('fake.event')); $manager->attach('inQ5', 'fake.event', array('priority' => 5)); $manager->attach('inQ1', 'fake.event', array('priority' => 1)); $manager->attach('otherInQ5', 'fake.event', array('priority' => 5)); $expected = array_merge( array( array('callable' => 'inQ1', 'passParams' => false), array('callable' => 'inQ5', 'passParams' => false), array('callable' => 'otherInQ5', 'passParams' => false) ), $expected ); $this->assertEquals($expected, $manager->listeners('fake.event')); } /** * Tests the attach() method for multiple event key in multiple queues * * @return void */ public function testAttachMultipleEventKeys() { $manager = new CakeEventManager(); $manager->attach('fakeFunction', 'fake.event'); $manager->attach('fakeFunction2', 'another.event'); $manager->attach('fakeFunction3', 'another.event', array('priority' => 1, 'passParams' => true)); $expected = array( array('callable' => 'fakeFunction', 'passParams' => false) ); $this->assertEquals($expected, $manager->listeners('fake.event')); $expected = array( array('callable' => 'fakeFunction3', 'passParams' => true), array('callable' => 'fakeFunction2', 'passParams' => false) ); $this->assertEquals($expected, $manager->listeners('another.event')); } /** * Tests detaching an event from a event key queue * * @return void */ public function testDetach() { $manager = new CakeEventManager(); $manager->attach(array('AClass', 'aMethod'), 'fake.event'); $manager->attach(array('AClass', 'anotherMethod'), 'another.event'); $manager->attach('fakeFunction', 'another.event', array('priority' => 1)); $manager->detach(array('AClass', 'aMethod'), 'fake.event'); $this->assertEquals(array(), $manager->listeners('fake.event')); $manager->detach(array('AClass', 'anotherMethod'), 'another.event'); $expected = array( array('callable' => 'fakeFunction', 'passParams' => false) ); $this->assertEquals($expected, $manager->listeners('another.event')); $manager->detach('fakeFunction', 'another.event'); $this->assertEquals(array(), $manager->listeners('another.event')); } /** * Tests detaching an event from all event queues * * @return void */ public function testDetachFromAll() { $manager = new CakeEventManager(); $manager->attach(array('AClass', 'aMethod'), 'fake.event'); $manager->attach(array('AClass', 'aMethod'), 'another.event'); $manager->attach('fakeFunction', 'another.event', array('priority' => 1)); $manager->detach(array('AClass', 'aMethod')); $expected = array( array('callable' => 'fakeFunction', 'passParams' => false) ); $this->assertEquals($expected, $manager->listeners('another.event')); $this->assertEquals(array(), $manager->listeners('fake.event')); } /** * Tests event dispatching * * @return void * @triggers fake.event */ public function testDispatch() { $manager = new CakeEventManager(); $listener = $this->getMock('CakeEventTestListener'); $anotherListener = $this->getMock('CakeEventTestListener'); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $manager->attach(array($anotherListener, 'listenerFunction'), 'fake.event'); $event = new CakeEvent('fake.event'); $listener->expects($this->once())->method('listenerFunction')->with($event); $anotherListener->expects($this->once())->method('listenerFunction')->with($event); $manager->dispatch($event); } /** * Tests event dispatching using event key name * * @return void */ public function testDispatchWithKeyName() { $manager = new CakeEventManager(); $listener = new CakeEventTestListener(); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $event = 'fake.event'; $manager->dispatch($event); $expected = array('listenerFunction'); $this->assertEquals($expected, $listener->callStack); } /** * Tests event dispatching with a return value * * @return void * @triggers fake.event */ public function testDispatchReturnValue() { $this->skipIf( version_compare(PHPUnit_Runner_Version::id(), '3.7', '<'), 'These tests fail in PHPUnit 3.6' ); $manager = new CakeEventManager(); $listener = $this->getMock('CakeEventTestListener'); $anotherListener = $this->getMock('CakeEventTestListener'); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $manager->attach(array($anotherListener, 'listenerFunction'), 'fake.event'); $event = new CakeEvent('fake.event'); $listener->expects($this->at(0))->method('listenerFunction') ->with($event) ->will($this->returnValue('something special')); $anotherListener->expects($this->at(0)) ->method('listenerFunction') ->with($event); $manager->dispatch($event); $this->assertEquals('something special', $event->result); } /** * Tests that returning false in a callback stops the event * * @return void * @triggers fake.event */ public function testDispatchFalseStopsEvent() { $this->skipIf( version_compare(PHPUnit_Runner_Version::id(), '3.7', '<'), 'These tests fail in PHPUnit 3.6' ); $manager = new CakeEventManager(); $listener = $this->getMock('CakeEventTestListener'); $anotherListener = $this->getMock('CakeEventTestListener'); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $manager->attach(array($anotherListener, 'listenerFunction'), 'fake.event'); $event = new CakeEvent('fake.event'); $listener->expects($this->at(0))->method('listenerFunction') ->with($event) ->will($this->returnValue(false)); $anotherListener->expects($this->never()) ->method('listenerFunction'); $manager->dispatch($event); $this->assertTrue($event->isStopped()); } /** * Tests event dispatching using priorities * * @return void * @triggers fake.event */ public function testDispatchPrioritized() { $manager = new CakeEventManager(); $listener = new CakeEventTestListener(); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $manager->attach(array($listener, 'secondListenerFunction'), 'fake.event', array('priority' => 5)); $event = new CakeEvent('fake.event'); $manager->dispatch($event); $expected = array('secondListenerFunction', 'listenerFunction'); $this->assertEquals($expected, $listener->callStack); } /** * Tests event dispatching with passed params * * @return void * @triggers fake.event $this, array('some' => 'data') */ public function testDispatchPassingParams() { $manager = new CakeEventManager(); $listener = $this->getMock('CakeEventTestListener'); $anotherListener = $this->getMock('CakeEventTestListener'); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $manager->attach(array($anotherListener, 'secondListenerFunction'), 'fake.event', array('passParams' => true)); $event = new CakeEvent('fake.event', $this, array('some' => 'data')); $listener->expects($this->once())->method('listenerFunction')->with($event); $anotherListener->expects($this->once())->method('secondListenerFunction')->with('data'); $manager->dispatch($event); } /** * Tests subscribing a listener object and firing the events it subscribed to * * @return void * @triggers fake.event * @triggers another.event $this, array('some' => 'data') * @triggers multiple.handlers */ public function testAttachSubscriber() { $manager = new CakeEventManager(); $listener = $this->getMock('CustomTestEventListener', array('secondListenerFunction')); $manager->attach($listener); $event = new CakeEvent('fake.event'); $manager->dispatch($event); $expected = array('listenerFunction'); $this->assertEquals($expected, $listener->callStack); $listener->expects($this->at(0))->method('secondListenerFunction')->with('data'); $event = new CakeEvent('another.event', $this, array('some' => 'data')); $manager->dispatch($event); $manager = new CakeEventManager(); $listener = $this->getMock('CustomTestEventListener', array('listenerFunction', 'thirdListenerFunction')); $manager->attach($listener); $event = new CakeEvent('multiple.handlers'); $listener->expects($this->once())->method('listenerFunction')->with($event); $listener->expects($this->once())->method('thirdListenerFunction')->with($event); $manager->dispatch($event); } /** * Tests subscribing a listener object and firing the events it subscribed to * * @return void */ public function testDetachSubscriber() { $manager = new CakeEventManager(); $listener = $this->getMock('CustomTestEventListener', array('secondListenerFunction')); $manager->attach($listener); $expected = array( array('callable' => array($listener, 'secondListenerFunction'), 'passParams' => true) ); $this->assertEquals($expected, $manager->listeners('another.event')); $expected = array( array('callable' => array($listener, 'listenerFunction'), 'passParams' => false) ); $this->assertEquals($expected, $manager->listeners('fake.event')); $manager->detach($listener); $this->assertEquals(array(), $manager->listeners('fake.event')); $this->assertEquals(array(), $manager->listeners('another.event')); } /** * Tests that it is possible to get/set the manager singleton * * @return void */ public function testGlobalDispatcherGetter() { $this->assertInstanceOf('CakeEventManager', CakeEventManager::instance()); $manager = new CakeEventManager(); CakeEventManager::instance($manager); $this->assertSame($manager, CakeEventManager::instance()); } /** * Tests that the global event manager gets the event too from any other manager * * @return void * @triggers fake.event */ public function testDispatchWithGlobal() { $generalManager = $this->getMock('CakeEventManager', array('prioritisedListeners')); $manager = new CakeEventManager(); $event = new CakeEvent('fake.event'); CakeEventManager::instance($generalManager); $generalManager->expects($this->once())->method('prioritisedListeners')->with('fake.event'); $manager->dispatch($event); CakeEventManager::instance(new CakeEventManager()); } /** * Tests that stopping an event will not notify the rest of the listeners * * @return void * @triggers fake.event */ public function testStopPropagation() { $generalManager = $this->getMock('CakeEventManager'); $manager = new CakeEventManager(); $listener = new CakeEventTestListener(); CakeEventManager::instance($generalManager); $generalManager->expects($this->any()) ->method('prioritisedListeners') ->with('fake.event') ->will($this->returnValue(array())); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $manager->attach(array($listener, 'stopListener'), 'fake.event', array('priority' => 8)); $manager->attach(array($listener, 'secondListenerFunction'), 'fake.event', array('priority' => 5)); $event = new CakeEvent('fake.event'); $manager->dispatch($event); $expected = array('secondListenerFunction'); $this->assertEquals($expected, $listener->callStack); CakeEventManager::instance(new CakeEventManager()); } /** * Tests event dispatching using priorities * * @return void * @triggers fake.event */ public function testDispatchPrioritizedWithGlobal() { $generalManager = $this->getMock('CakeEventManager'); $manager = new CakeEventManager(); $listener = new CustomTestEventListener(); $event = new CakeEvent('fake.event'); CakeEventManager::instance($generalManager); $generalManager->expects($this->any()) ->method('prioritisedListeners') ->with('fake.event') ->will($this->returnValue( array(11 => array( array('callable' => array($listener, 'secondListenerFunction'), 'passParams' => false) )) )); $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); $manager->attach(array($listener, 'thirdListenerFunction'), 'fake.event', array('priority' => 15)); $manager->dispatch($event); $expected = array('listenerFunction', 'secondListenerFunction', 'thirdListenerFunction'); $this->assertEquals($expected, $listener->callStack); CakeEventManager::instance(new CakeEventManager()); } /** * Tests event dispatching using priorities * * @return void * @triggers fake.event */ public function testDispatchGlobalBeforeLocal() { $generalManager = $this->getMock('CakeEventManager'); $manager = new CakeEventManager(); $listener = new CustomTestEventListener(); $event = new CakeEvent('fake.event'); CakeEventManager::instance($generalManager); $generalManager->expects($this->any()) ->method('prioritisedListeners') ->with('fake.event') ->will($this->returnValue( array(10 => array( array('callable' => array($listener, 'listenerFunction'), 'passParams' => false) )) )); $manager->attach(array($listener, 'secondListenerFunction'), 'fake.event'); $manager->dispatch($event); $expected = array('listenerFunction', 'secondListenerFunction'); $this->assertEquals($expected, $listener->callStack); CakeEventManager::instance(new CakeEventManager()); } /** * test callback */ public function onMyEvent($event) { $event->data['callback'] = 'ok'; } /** * Tests events dispatched by a local manager can be handled by * handler registered in the global event manager * @triggers my_event $manager */ public function testDispatchLocalHandledByGlobal() { $callback = array($this, 'onMyEvent'); CakeEventManager::instance()->attach($callback, 'my_event'); $manager = new CakeEventManager(); $event = new CakeEvent('my_event', $manager); $manager->dispatch($event); $this->assertEquals('ok', $event->data['callback']); } /** * Test that events are dispatched properly when there are global and local * listeners at the same priority. * * @return void * @triggers fake.event $this */ public function testDispatchWithGlobalAndLocalEvents() { $listener = new CustomTestEventListener(); CakeEventManager::instance()->attach($listener); $listener2 = new CakeEventTestListener(); $manager = new CakeEventManager(); $manager->attach(array($listener2, 'listenerFunction'), 'fake.event'); $manager->dispatch(new CakeEvent('fake.event', $this)); $this->assertEquals(array('listenerFunction'), $listener->callStack); $this->assertEquals(array('listenerFunction'), $listener2->callStack); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllDatabaseTest.php0000644000000000000000000000323713365153155022622 0ustar rootrootaddTestFile($path . $task . 'Test.php'); } return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/I18n/0000755000000000000000000000000013365153155017626 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/I18n/MultibyteTest.php0000644000000000000000000137115513365153155023172 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.I18n * @since CakePHP(tm) v 1.2.0.6833 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Multibyte', 'I18n'); /** * MultibyteTest class * * @package Cake.Test.Case.I18n */ class MultibyteTest extends CakeTestCase { /** * testUtf8 method * * @return void */ public function testUtf8() { $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = Multibyte::utf8($string); $expected = array(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126); $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $result = Multibyte::utf8($string); $expected = array(161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200); $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $result = Multibyte::utf8($string); $expected = array(201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300); $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $result = Multibyte::utf8($string); $expected = array(301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400); $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $result = Multibyte::utf8($string); $expected = array(401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500); $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $result = Multibyte::utf8($string); $expected = array(601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700); $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $result = Multibyte::utf8($string); $expected = array(1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051); $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $result = Multibyte::utf8($string); $expected = array(1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100); $this->assertEquals($expected, $result); $string = 'ีนีบีปีผีฝีพีฟ'; $result = Multibyte::utf8($string); $expected = array(1401, 1402, 1403, 1404, 1405, 1406, 1407); $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $result = Multibyte::utf8($string); $expected = array(1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615); $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $result = Multibyte::utf8($string); $expected = array(10032, 10033, 10034, 10035, 10036, 10037, 10038, 10039, 10040, 10041, 10042, 10043, 10044, 10045, 10046, 10047, 10048, 10049, 10050, 10051, 10052, 10053, 10054, 10055, 10056, 10057, 10058, 10059, 10060, 10061, 10062, 10063, 10064, 10065, 10066, 10067, 10068, 10069, 10070, 10071, 10072, 10073, 10074, 10075, 10076, 10077, 10078); $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $result = Multibyte::utf8($string); $expected = array(11904, 11905, 11906, 11907, 11908, 11909, 11910, 11911, 11912, 11913, 11914, 11915, 11916, 11917, 11918, 11919, 11920, 11921, 11922, 11923, 11924, 11925, 11926, 11927, 11928, 11929, 11931, 11932, 11933, 11934, 11935, 11936, 11937, 11938, 11939, 11940, 11941, 11942, 11943, 11944, 11945, 11946, 11947, 11948, 11949, 11950, 11951, 11952, 11953, 11954, 11955, 11956, 11957, 11958, 11959, 11960, 11961, 11962, 11963, 11964, 11965, 11966, 11967, 11968, 11969, 11970, 11971, 11972, 11973, 11974, 11975, 11976, 11977, 11978, 11979, 11980, 11981, 11982, 11983, 11984, 11985, 11986, 11987, 11988, 11989, 11990, 11991, 11992, 11993, 11994, 11995, 11996, 11997, 11998, 11999, 12000); $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $result = Multibyte::utf8($string); $expected = array(12101, 12102, 12103, 12104, 12105, 12106, 12107, 12108, 12109, 12110, 12111, 12112, 12113, 12114, 12115, 12116, 12117, 12118, 12119, 12120, 12121, 12122, 12123, 12124, 12125, 12126, 12127, 12128, 12129, 12130, 12131, 12132, 12133, 12134, 12135, 12136, 12137, 12138, 12139, 12140, 12141, 12142, 12143, 12144, 12145, 12146, 12147, 12148, 12149, 12150, 12151, 12152, 12153, 12154, 12155, 12156, 12157, 12158, 12159); $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $result = Multibyte::utf8($string); $expected = array(45601, 45602, 45603, 45604, 45605, 45606, 45607, 45608, 45609, 45610, 45611, 45612, 45613, 45614, 45615, 45616, 45617, 45618, 45619, 45620, 45621, 45622, 45623, 45624, 45625, 45626, 45627, 45628, 45629, 45630, 45631, 45632, 45633, 45634, 45635, 45636, 45637, 45638, 45639, 45640, 45641, 45642, 45643, 45644, 45645, 45646, 45647, 45648, 45649, 45650, 45651, 45652, 45653, 45654, 45655, 45656, 45657, 45658, 45659, 45660, 45661, 45662, 45663, 45664, 45665, 45666, 45667, 45668, 45669, 45670, 45671, 45672, 45673, 45674, 45675, 45676, 45677, 45678, 45679, 45680, 45681, 45682, 45683, 45684, 45685, 45686, 45687, 45688, 45689, 45690, 45691, 45692, 45693, 45694, 45695, 45696, 45697, 45698, 45699, 45700); $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $result = Multibyte::utf8($string); $expected = array(65136, 65137, 65138, 65139, 65140, 65141, 65142, 65143, 65144, 65145, 65146, 65147, 65148, 65149, 65150, 65151, 65152, 65153, 65154, 65155, 65156, 65157, 65158, 65159, 65160, 65161, 65162, 65163, 65164, 65165, 65166, 65167, 65168, 65169, 65170, 65171, 65172, 65173, 65174, 65175, 65176, 65177, 65178, 65179, 65180, 65181, 65182, 65183, 65184, 65185, 65186, 65187, 65188, 65189, 65190, 65191, 65192, 65193, 65194, 65195, 65196, 65197, 65198, 65199, 65200); $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $result = Multibyte::utf8($string); $expected = array(65201, 65202, 65203, 65204, 65205, 65206, 65207, 65208, 65209, 65210, 65211, 65212, 65213, 65214, 65215, 65216, 65217, 65218, 65219, 65220, 65221, 65222, 65223, 65224, 65225, 65226, 65227, 65228, 65229, 65230, 65231, 65232, 65233, 65234, 65235, 65236, 65237, 65238, 65239, 65240, 65241, 65242, 65243, 65244, 65245, 65246, 65247, 65248, 65249, 65250, 65251, 65252, 65253, 65254, 65255, 65256, 65257, 65258, 65259, 65260, 65261, 65262, 65263, 65264, 65265, 65266, 65267, 65268, 65269, 65270, 65271, 65272, 65273, 65274, 65275, 65276); $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $result = Multibyte::utf8($string); $expected = array(65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370); $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $result = Multibyte::utf8($string); $expected = array(65377, 65378, 65379, 65380, 65381, 65382, 65383, 65384, 65385, 65386, 65387, 65388, 65389, 65390, 65391, 65392, 65393, 65394, 65395, 65396, 65397, 65398, 65399, 65400); $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $result = Multibyte::utf8($string); $expected = array(65401, 65402, 65403, 65404, 65405, 65406, 65407, 65408, 65409, 65410, 65411, 65412, 65413, 65414, 65415, 65416, 65417, 65418, 65419, 65420, 65421, 65422, 65423, 65424, 65425, 65426, 65427, 65428, 65429, 65430, 65431, 65432, 65433, 65434, 65435, 65436, 65437, 65438); $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = Multibyte::utf8($string); $expected = array(292, 275, 314, 316, 335, 44, 32, 372, 337, 345, 316, 271, 33); $this->assertEquals($expected, $result); $string = 'Hello, World!'; $result = Multibyte::utf8($string); $expected = array(72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33); $this->assertEquals($expected, $result); $string = 'ยจ'; $result = Multibyte::utf8($string); $expected = array(168); $this->assertEquals($expected, $result); $string = 'ยฟ'; $result = Multibyte::utf8($string); $expected = array(191); $this->assertEquals($expected, $result); $string = 'ฤini'; $result = Multibyte::utf8($string); $expected = array(269, 105, 110, 105); $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $result = Multibyte::utf8($string); $expected = array(109, 111, 263, 105); $this->assertEquals($expected, $result); $string = 'drลพavni'; $result = Multibyte::utf8($string); $expected = array(100, 114, 382, 97, 118, 110, 105); $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $result = Multibyte::utf8($string); $expected = array(25226, 30334, 24230, 35774, 20026, 39318, 39029); $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = Multibyte::utf8($string); $expected = array(19968, 20108, 19977, 21608, 27704, 40845); $this->assertEquals($expected, $result); $string = 'ิ€ิ‚ิ„ิ†ิˆิŠิŒิŽิิ’'; $result = Multibyte::utf8($string); $expected = array(1280, 1282, 1284, 1286, 1288, 1290, 1292, 1294, 1296, 1298); $this->assertEquals($expected, $result); $string = 'ิิƒิ…ิ‡ิ‰ิ‹ิิิิ’'; $result = Multibyte::utf8($string); $expected = array(1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1296, 1298); $this->assertEquals($expected, $result); $string = 'ิฑิฒิณิดิติถิทิธินิบิปิผิฝิพิฟี€ีี‚ีƒี„ี…ี†ี‡ีˆี‰ีŠี‹ีŒีีŽีีี‘ี’ี“ี”ี•ี–ึ‡'; $result = Multibyte::utf8($string); $expected = array(1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 1415); $this->assertEquals($expected, $result); $string = 'ีกีขีฃีคีฅีฆีงีจีฉีชีซีฌีญีฎีฏีฐีฑีฒีณีดีตีถีทีธีนีบีปีผีฝีพีฟึ€ึึ‚ึƒึ„ึ…ึ†ึ‡'; $result = Multibyte::utf8($string); $expected = array(1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415); $this->assertEquals($expected, $result); $string = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $result = Multibyte::utf8($string); $expected = array(4256, 4257, 4258, 4259, 4260, 4261, 4262, 4263, 4264, 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, 4274, 4275, 4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, 4287, 4288, 4289, 4290, 4291, 4292, 4293); $this->assertEquals($expected, $result); $string = 'แธ€แธ‚แธ„แธ†แธˆแธŠแธŒแธŽแธแธ’แธ”แธ–แธ˜แธšแธœแธžแธ แธขแธคแธฆแธจแธชแธฌแธฎแธฐแธฒแธดแธถแธธแธบแธผแธพแน€แน‚แน„แน†แนˆแนŠแนŒแนŽแนแน’แน”แน–แน˜แนšแนœแนžแน แนขแนคแนฆแนจแนชแนฌแนฎแนฐแนฒแนดแนถแนธแนบแนผแนพแบ€แบ‚แบ„แบ†แบˆแบŠแบŒแบŽแบแบ’แบ”แบ–แบ—แบ˜แบ™แบšแบ แบขแบคแบฆแบจแบชแบฌแบฎแบฐแบฒแบดแบถแบธแบบแบผแบพแป€แป‚แป„แป†แปˆแปŠแปŒแปŽแปแป’แป”แป–แป˜แปšแปœแปžแป แปขแปคแปฆแปจแปชแปฌแปฎแปฐแปฒแปดแปถแปธ'; $result = Multibyte::utf8($string); $expected = array(7680, 7682, 7684, 7686, 7688, 7690, 7692, 7694, 7696, 7698, 7700, 7702, 7704, 7706, 7708, 7710, 7712, 7714, 7716, 7718, 7720, 7722, 7724, 7726, 7728, 7730, 7732, 7734, 7736, 7738, 7740, 7742, 7744, 7746, 7748, 7750, 7752, 7754, 7756, 7758, 7760, 7762, 7764, 7766, 7768, 7770, 7772, 7774, 7776, 7778, 7780, 7782, 7784, 7786, 7788, 7790, 7792, 7794, 7796, 7798, 7800, 7802, 7804, 7806, 7808, 7810, 7812, 7814, 7816, 7818, 7820, 7822, 7824, 7826, 7828, 7830, 7831, 7832, 7833, 7834, 7840, 7842, 7844, 7846, 7848, 7850, 7852, 7854, 7856, 7858, 7860, 7862, 7864, 7866, 7868, 7870, 7872, 7874, 7876, 7878, 7880, 7882, 7884, 7886, 7888, 7890, 7892, 7894, 7896, 7898, 7900, 7902, 7904, 7906, 7908, 7910, 7912, 7914, 7916, 7918, 7920, 7922, 7924, 7926, 7928); $this->assertEquals($expected, $result); $string = 'แธแธƒแธ…แธ‡แธ‰แธ‹แธแธแธ‘แธ“แธ•แธ—แธ™แธ›แธแธŸแธกแธฃแธฅแธงแธฉแธซแธญแธฏแธฑแธณแธตแธทแธนแธปแธฝแธฟแนแนƒแน…แน‡แน‰แน‹แนแนแน‘แน“แน•แน—แน™แน›แนแนŸแนกแนฃแนฅแนงแนฉแนซแนญแนฏแนฑแนณแนตแนทแนนแนปแนฝแนฟแบแบƒแบ…แบ‡แบ‰แบ‹แบแบแบ‘แบ“แบ•แบ–แบ—แบ˜แบ™แบšแบกแบฃแบฅแบงแบฉแบซแบญแบฏแบฑแบณแบตแบทแบนแบปแบฝแบฟแปแปƒแป…แป‡แป‰แป‹แปแปแป‘แป“แป•แป—แป™แป›แปแปŸแปกแปฃแปฅแปงแปฉแปซแปญแปฏแปฑแปณแปตแปทแปน'; $result = Multibyte::utf8($string); $expected = array(7681, 7683, 7685, 7687, 7689, 7691, 7693, 7695, 7697, 7699, 7701, 7703, 7705, 7707, 7709, 7711, 7713, 7715, 7717, 7719, 7721, 7723, 7725, 7727, 7729, 7731, 7733, 7735, 7737, 7739, 7741, 7743, 7745, 7747, 7749, 7751, 7753, 7755, 7757, 7759, 7761, 7763, 7765, 7767, 7769, 7771, 7773, 7775, 7777, 7779, 7781, 7783, 7785, 7787, 7789, 7791, 7793, 7795, 7797, 7799, 7801, 7803, 7805, 7807, 7809, 7811, 7813, 7815, 7817, 7819, 7821, 7823, 7825, 7827, 7829, 7830, 7831, 7832, 7833, 7834, 7841, 7843, 7845, 7847, 7849, 7851, 7853, 7855, 7857, 7859, 7861, 7863, 7865, 7867, 7869, 7871, 7873, 7875, 7877, 7879, 7881, 7883, 7885, 7887, 7889, 7891, 7893, 7895, 7897, 7899, 7901, 7903, 7905, 7907, 7909, 7911, 7913, 7915, 7917, 7919, 7921, 7923, 7925, 7927, 7929); $this->assertEquals($expected, $result); $string = 'โ„ฆโ„ชโ„ซโ„ฒ'; $result = Multibyte::utf8($string); $expected = array(8486, 8490, 8491, 8498); $this->assertEquals($expected, $result); $string = 'ฯ‰kรฅโ…Ž'; $result = Multibyte::utf8($string); $expected = array(969, 107, 229, 8526); $this->assertEquals($expected, $result); $string = 'โ… โ…กโ…ขโ…ฃโ…คโ…ฅโ…ฆโ…งโ…จโ…ฉโ…ชโ…ซโ…ฌโ…ญโ…ฎโ…ฏโ†ƒ'; $result = Multibyte::utf8($string); $expected = array(8544, 8545, 8546, 8547, 8548, 8549, 8550, 8551, 8552, 8553, 8554, 8555, 8556, 8557, 8558, 8559, 8579); $this->assertEquals($expected, $result); $string = 'โ…ฐโ…ฑโ…ฒโ…ณโ…ดโ…ตโ…ถโ…ทโ…ธโ…นโ…บโ…ปโ…ผโ…ฝโ…พโ…ฟโ†„'; $result = Multibyte::utf8($string); $expected = array(8560, 8561, 8562, 8563, 8564, 8565, 8566, 8567, 8568, 8569, 8570, 8571, 8572, 8573, 8574, 8575, 8580); $this->assertEquals($expected, $result); $string = 'โ’ถโ’ทโ’ธโ’นโ’บโ’ปโ’ผโ’ฝโ’พโ’ฟโ“€โ“โ“‚โ“ƒโ“„โ“…โ“†โ“‡โ“ˆโ“‰โ“Šโ“‹โ“Œโ“โ“Žโ“'; $result = Multibyte::utf8($string); $expected = array(9398, 9399, 9400, 9401, 9402, 9403, 9404, 9405, 9406, 9407, 9408, 9409, 9410, 9411, 9412, 9413, 9414, 9415, 9416, 9417, 9418, 9419, 9420, 9421, 9422, 9423); $this->assertEquals($expected, $result); $string = 'โ“โ“‘โ“’โ““โ“”โ“•โ“–โ“—โ“˜โ“™โ“šโ“›โ“œโ“โ“žโ“Ÿโ“ โ“กโ“ขโ“ฃโ“คโ“ฅโ“ฆโ“งโ“จโ“ฉ'; $result = Multibyte::utf8($string); $expected = array(9424, 9425, 9426, 9427, 9428, 9429, 9430, 9431, 9432, 9433, 9434, 9435, 9436, 9437, 9438, 9439, 9440, 9441, 9442, 9443, 9444, 9445, 9446, 9447, 9448, 9449); $this->assertEquals($expected, $result); $string = 'โฐ€โฐโฐ‚โฐƒโฐ„โฐ…โฐ†โฐ‡โฐˆโฐ‰โฐŠโฐ‹โฐŒโฐโฐŽโฐโฐโฐ‘โฐ’โฐ“โฐ”โฐ•โฐ–โฐ—โฐ˜โฐ™โฐšโฐ›โฐœโฐโฐžโฐŸโฐ โฐกโฐขโฐฃโฐคโฐฅโฐฆโฐงโฐจโฐฉโฐชโฐซโฐฌโฐญโฐฎ'; $result = Multibyte::utf8($string); $expected = array(11264, 11265, 11266, 11267, 11268, 11269, 11270, 11271, 11272, 11273, 11274, 11275, 11276, 11277, 11278, 11279, 11280, 11281, 11282, 11283, 11284, 11285, 11286, 11287, 11288, 11289, 11290, 11291, 11292, 11293, 11294, 11295, 11296, 11297, 11298, 11299, 11300, 11301, 11302, 11303, 11304, 11305, 11306, 11307, 11308, 11309, 11310); $this->assertEquals($expected, $result); $string = 'โฐฐโฐฑโฐฒโฐณโฐดโฐตโฐถโฐทโฐธโฐนโฐบโฐปโฐผโฐฝโฐพโฐฟโฑ€โฑโฑ‚โฑƒโฑ„โฑ…โฑ†โฑ‡โฑˆโฑ‰โฑŠโฑ‹โฑŒโฑโฑŽโฑโฑโฑ‘โฑ’โฑ“โฑ”โฑ•โฑ–โฑ—โฑ˜โฑ™โฑšโฑ›โฑœโฑโฑž'; $result = Multibyte::utf8($string); $expected = array(11312, 11313, 11314, 11315, 11316, 11317, 11318, 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326, 11327, 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, 11339, 11340, 11341, 11342, 11343, 11344, 11345, 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358); $this->assertEquals($expected, $result); $string = 'โฒ€โฒ‚โฒ„โฒ†โฒˆโฒŠโฒŒโฒŽโฒโฒ’โฒ”โฒ–โฒ˜โฒšโฒœโฒžโฒ โฒขโฒคโฒฆโฒจโฒชโฒฌโฒฎโฒฐโฒฒโฒดโฒถโฒธโฒบโฒผโฒพโณ€โณ‚โณ„โณ†โณˆโณŠโณŒโณŽโณโณ’โณ”โณ–โณ˜โณšโณœโณžโณ โณข'; $result = Multibyte::utf8($string); $expected = array(11392, 11394, 11396, 11398, 11400, 11402, 11404, 11406, 11408, 11410, 11412, 11414, 11416, 11418, 11420, 11422, 11424, 11426, 11428, 11430, 11432, 11434, 11436, 11438, 11440, 11442, 11444, 11446, 11448, 11450, 11452, 11454, 11456, 11458, 11460, 11462, 11464, 11466, 11468, 11470, 11472, 11474, 11476, 11478, 11480, 11482, 11484, 11486, 11488, 11490); $this->assertEquals($expected, $result); $string = 'โฒโฒƒโฒ…โฒ‡โฒ‰โฒ‹โฒโฒโฒ‘โฒ“โฒ•โฒ—โฒ™โฒ›โฒโฒŸโฒกโฒฃโฒฅโฒงโฒฉโฒซโฒญโฒฏโฒฑโฒณโฒตโฒทโฒนโฒปโฒฝโฒฟโณโณƒโณ…โณ‡โณ‰โณ‹โณโณโณ‘โณ“โณ•โณ—โณ™โณ›โณโณŸโณกโณฃ'; $result = Multibyte::utf8($string); $expected = array(11393, 11395, 11397, 11399, 11401, 11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419, 11421, 11423, 11425, 11427, 11429, 11431, 11433, 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449, 11451, 11453, 11455, 11457, 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473, 11475, 11477, 11479, 11481, 11483, 11485, 11487, 11489, 11491); $this->assertEquals($expected, $result); $string = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $result = Multibyte::utf8($string); $expected = array(64256, 64257, 64258, 64259, 64260, 64261, 64262, 64275, 64276, 64277, 64278, 64279); $this->assertEquals($expected, $result); } /** * testAscii method * * @return void */ public function testAscii() { $input = array(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126); $result = Multibyte::ascii($input); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $input = array(161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200); $result = Multibyte::ascii($input); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $input = array(201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300); $result = Multibyte::ascii($input); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $input = array(301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100); $expected = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(1401, 1402, 1403, 1404, 1405, 1406, 1407); $expected = 'ีนีบีปีผีฝีพีฟ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615); $expected = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(10032, 10033, 10034, 10035, 10036, 10037, 10038, 10039, 10040, 10041, 10042, 10043, 10044, 10045, 10046, 10047, 10048, 10049, 10050, 10051, 10052, 10053, 10054, 10055, 10056, 10057, 10058, 10059, 10060, 10061, 10062, 10063, 10064, 10065, 10066, 10067, 10068, 10069, 10070, 10071, 10072, 10073, 10074, 10075, 10076, 10077, 10078); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(11904, 11905, 11906, 11907, 11908, 11909, 11910, 11911, 11912, 11913, 11914, 11915, 11916, 11917, 11918, 11919, 11920, 11921, 11922, 11923, 11924, 11925, 11926, 11927, 11928, 11929, 11931, 11932, 11933, 11934, 11935, 11936, 11937, 11938, 11939, 11940, 11941, 11942, 11943, 11944, 11945, 11946, 11947, 11948, 11949, 11950, 11951, 11952, 11953, 11954, 11955, 11956, 11957, 11958, 11959, 11960, 11961, 11962, 11963, 11964, 11965, 11966, 11967, 11968, 11969, 11970, 11971, 11972, 11973, 11974, 11975, 11976, 11977, 11978, 11979, 11980, 11981, 11982, 11983, 11984, 11985, 11986, 11987, 11988, 11989, 11990, 11991, 11992, 11993, 11994, 11995, 11996, 11997, 11998, 11999, 12000); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(12101, 12102, 12103, 12104, 12105, 12106, 12107, 12108, 12109, 12110, 12111, 12112, 12113, 12114, 12115, 12116, 12117, 12118, 12119, 12120, 12121, 12122, 12123, 12124, 12125, 12126, 12127, 12128, 12129, 12130, 12131, 12132, 12133, 12134, 12135, 12136, 12137, 12138, 12139, 12140, 12141, 12142, 12143, 12144, 12145, 12146, 12147, 12148, 12149, 12150, 12151, 12152, 12153, 12154, 12155, 12156, 12157, 12158, 12159); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(45601, 45602, 45603, 45604, 45605, 45606, 45607, 45608, 45609, 45610, 45611, 45612, 45613, 45614, 45615, 45616, 45617, 45618, 45619, 45620, 45621, 45622, 45623, 45624, 45625, 45626, 45627, 45628, 45629, 45630, 45631, 45632, 45633, 45634, 45635, 45636, 45637, 45638, 45639, 45640, 45641, 45642, 45643, 45644, 45645, 45646, 45647, 45648, 45649, 45650, 45651, 45652, 45653, 45654, 45655, 45656, 45657, 45658, 45659, 45660, 45661, 45662, 45663, 45664, 45665, 45666, 45667, 45668, 45669, 45670, 45671, 45672, 45673, 45674, 45675, 45676, 45677, 45678, 45679, 45680, 45681, 45682, 45683, 45684, 45685, 45686, 45687, 45688, 45689, 45690, 45691, 45692, 45693, 45694, 45695, 45696, 45697, 45698, 45699, 45700); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(65136, 65137, 65138, 65139, 65140, 65141, 65142, 65143, 65144, 65145, 65146, 65147, 65148, 65149, 65150, 65151, 65152, 65153, 65154, 65155, 65156, 65157, 65158, 65159, 65160, 65161, 65162, 65163, 65164, 65165, 65166, 65167, 65168, 65169, 65170, 65171, 65172, 65173, 65174, 65175, 65176, 65177, 65178, 65179, 65180, 65181, 65182, 65183, 65184, 65185, 65186, 65187, 65188, 65189, 65190, 65191, 65192, 65193, 65194, 65195, 65196, 65197, 65198, 65199, 65200); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(65201, 65202, 65203, 65204, 65205, 65206, 65207, 65208, 65209, 65210, 65211, 65212, 65213, 65214, 65215, 65216, 65217, 65218, 65219, 65220, 65221, 65222, 65223, 65224, 65225, 65226, 65227, 65228, 65229, 65230, 65231, 65232, 65233, 65234, 65235, 65236, 65237, 65238, 65239, 65240, 65241, 65242, 65243, 65244, 65245, 65246, 65247, 65248, 65249, 65250, 65251, 65252, 65253, 65254, 65255, 65256, 65257, 65258, 65259, 65260, 65261, 65262, 65263, 65264, 65265, 65266, 65267, 65268, 65269, 65270, 65271, 65272, 65273, 65274, 65275, 65276); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(65377, 65378, 65379, 65380, 65381, 65382, 65383, 65384, 65385, 65386, 65387, 65388, 65389, 65390, 65391, 65392, 65393, 65394, 65395, 65396, 65397, 65398, 65399, 65400); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(65401, 65402, 65403, 65404, 65405, 65406, 65407, 65408, 65409, 65410, 65411, 65412, 65413, 65414, 65415, 65416, 65417, 65418, 65419, 65420, 65421, 65422, 65423, 65424, 65425, 65426, 65427, 65428, 65429, 65430, 65431, 65432, 65433, 65434, 65435, 65436, 65437, 65438); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(292, 275, 314, 316, 335, 44, 32, 372, 337, 345, 316, 271, 33); $expected = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33); $expected = 'Hello, World!'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(168); $expected = 'ยจ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(191); $expected = 'ยฟ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(269, 105, 110, 105); $expected = 'ฤini'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(109, 111, 263, 105); $expected = 'moฤ‡i'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(100, 114, 382, 97, 118, 110, 105); $expected = 'drลพavni'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(25226, 30334, 24230, 35774, 20026, 39318, 39029); $expected = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(19968, 20108, 19977, 21608, 27704, 40845); $expected = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(1280, 1282, 1284, 1286, 1288, 1290, 1292, 1294, 1296, 1298); $expected = 'ิ€ิ‚ิ„ิ†ิˆิŠิŒิŽิิ’'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1296, 1298); $expected = 'ิิƒิ…ิ‡ิ‰ิ‹ิิิิ’'; $result = Multibyte::ascii($input); $this->assertEquals($expected, $result); $input = array(1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 1415); $result = Multibyte::ascii($input); $expected = 'ิฑิฒิณิดิติถิทิธินิบิปิผิฝิพิฟี€ีี‚ีƒี„ี…ี†ี‡ีˆี‰ีŠี‹ีŒีีŽีีี‘ี’ี“ี”ี•ี–ึ‡'; $this->assertEquals($expected, $result); $input = array(1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415); $result = Multibyte::ascii($input); $expected = 'ีกีขีฃีคีฅีฆีงีจีฉีชีซีฌีญีฎีฏีฐีฑีฒีณีดีตีถีทีธีนีบีปีผีฝีพีฟึ€ึึ‚ึƒึ„ึ…ึ†ึ‡'; $this->assertEquals($expected, $result); $input = array(4256, 4257, 4258, 4259, 4260, 4261, 4262, 4263, 4264, 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, 4274, 4275, 4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, 4287, 4288, 4289, 4290, 4291, 4292, 4293); $result = Multibyte::ascii($input); $expected = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $this->assertEquals($expected, $result); $input = array(7680, 7682, 7684, 7686, 7688, 7690, 7692, 7694, 7696, 7698, 7700, 7702, 7704, 7706, 7708, 7710, 7712, 7714, 7716, 7718, 7720, 7722, 7724, 7726, 7728, 7730, 7732, 7734, 7736, 7738, 7740, 7742, 7744, 7746, 7748, 7750, 7752, 7754, 7756, 7758, 7760, 7762, 7764, 7766, 7768, 7770, 7772, 7774, 7776, 7778, 7780, 7782, 7784, 7786, 7788, 7790, 7792, 7794, 7796, 7798, 7800, 7802, 7804, 7806, 7808, 7810, 7812, 7814, 7816, 7818, 7820, 7822, 7824, 7826, 7828, 7830, 7831, 7832, 7833, 7834, 7840, 7842, 7844, 7846, 7848, 7850, 7852, 7854, 7856, 7858, 7860, 7862, 7864, 7866, 7868, 7870, 7872, 7874, 7876, 7878, 7880, 7882, 7884, 7886, 7888, 7890, 7892, 7894, 7896, 7898, 7900, 7902, 7904, 7906, 7908, 7910, 7912, 7914, 7916, 7918, 7920, 7922, 7924, 7926, 7928); $result = Multibyte::ascii($input); $expected = 'แธ€แธ‚แธ„แธ†แธˆแธŠแธŒแธŽแธแธ’แธ”แธ–แธ˜แธšแธœแธžแธ แธขแธคแธฆแธจแธชแธฌแธฎแธฐแธฒแธดแธถแธธแธบแธผแธพแน€แน‚แน„แน†แนˆแนŠแนŒแนŽแนแน’แน”แน–แน˜แนšแนœแนžแน แนขแนคแนฆแนจแนชแนฌแนฎแนฐแนฒแนดแนถแนธแนบแนผแนพแบ€แบ‚แบ„แบ†แบˆแบŠแบŒแบŽแบแบ’แบ”แบ–แบ—แบ˜แบ™แบšแบ แบขแบคแบฆแบจแบชแบฌแบฎแบฐแบฒแบดแบถแบธแบบแบผแบพแป€แป‚แป„แป†แปˆแปŠแปŒแปŽแปแป’แป”แป–แป˜แปšแปœแปžแป แปขแปคแปฆแปจแปชแปฌแปฎแปฐแปฒแปดแปถแปธ'; $this->assertEquals($expected, $result); $input = array(7681, 7683, 7685, 7687, 7689, 7691, 7693, 7695, 7697, 7699, 7701, 7703, 7705, 7707, 7709, 7711, 7713, 7715, 7717, 7719, 7721, 7723, 7725, 7727, 7729, 7731, 7733, 7735, 7737, 7739, 7741, 7743, 7745, 7747, 7749, 7751, 7753, 7755, 7757, 7759, 7761, 7763, 7765, 7767, 7769, 7771, 7773, 7775, 7777, 7779, 7781, 7783, 7785, 7787, 7789, 7791, 7793, 7795, 7797, 7799, 7801, 7803, 7805, 7807, 7809, 7811, 7813, 7815, 7817, 7819, 7821, 7823, 7825, 7827, 7829, 7830, 7831, 7832, 7833, 7834, 7841, 7843, 7845, 7847, 7849, 7851, 7853, 7855, 7857, 7859, 7861, 7863, 7865, 7867, 7869, 7871, 7873, 7875, 7877, 7879, 7881, 7883, 7885, 7887, 7889, 7891, 7893, 7895, 7897, 7899, 7901, 7903, 7905, 7907, 7909, 7911, 7913, 7915, 7917, 7919, 7921, 7923, 7925, 7927, 7929); $result = Multibyte::ascii($input); $expected = 'แธแธƒแธ…แธ‡แธ‰แธ‹แธแธแธ‘แธ“แธ•แธ—แธ™แธ›แธแธŸแธกแธฃแธฅแธงแธฉแธซแธญแธฏแธฑแธณแธตแธทแธนแธปแธฝแธฟแนแนƒแน…แน‡แน‰แน‹แนแนแน‘แน“แน•แน—แน™แน›แนแนŸแนกแนฃแนฅแนงแนฉแนซแนญแนฏแนฑแนณแนตแนทแนนแนปแนฝแนฟแบแบƒแบ…แบ‡แบ‰แบ‹แบแบแบ‘แบ“แบ•แบ–แบ—แบ˜แบ™แบšแบกแบฃแบฅแบงแบฉแบซแบญแบฏแบฑแบณแบตแบทแบนแบปแบฝแบฟแปแปƒแป…แป‡แป‰แป‹แปแปแป‘แป“แป•แป—แป™แป›แปแปŸแปกแปฃแปฅแปงแปฉแปซแปญแปฏแปฑแปณแปตแปทแปน'; $this->assertEquals($expected, $result); $input = array(8486, 8490, 8491, 8498); $result = Multibyte::ascii($input); $expected = 'โ„ฆโ„ชโ„ซโ„ฒ'; $this->assertEquals($expected, $result); $input = array(969, 107, 229, 8526); $result = Multibyte::ascii($input); $expected = 'ฯ‰kรฅโ…Ž'; $this->assertEquals($expected, $result); $input = array(8544, 8545, 8546, 8547, 8548, 8549, 8550, 8551, 8552, 8553, 8554, 8555, 8556, 8557, 8558, 8559, 8579); $result = Multibyte::ascii($input); $expected = 'โ… โ…กโ…ขโ…ฃโ…คโ…ฅโ…ฆโ…งโ…จโ…ฉโ…ชโ…ซโ…ฌโ…ญโ…ฎโ…ฏโ†ƒ'; $this->assertEquals($expected, $result); $input = array(8560, 8561, 8562, 8563, 8564, 8565, 8566, 8567, 8568, 8569, 8570, 8571, 8572, 8573, 8574, 8575, 8580); $result = Multibyte::ascii($input); $expected = 'โ…ฐโ…ฑโ…ฒโ…ณโ…ดโ…ตโ…ถโ…ทโ…ธโ…นโ…บโ…ปโ…ผโ…ฝโ…พโ…ฟโ†„'; $this->assertEquals($expected, $result); $input = array(9398, 9399, 9400, 9401, 9402, 9403, 9404, 9405, 9406, 9407, 9408, 9409, 9410, 9411, 9412, 9413, 9414, 9415, 9416, 9417, 9418, 9419, 9420, 9421, 9422, 9423); $result = Multibyte::ascii($input); $expected = 'โ’ถโ’ทโ’ธโ’นโ’บโ’ปโ’ผโ’ฝโ’พโ’ฟโ“€โ“โ“‚โ“ƒโ“„โ“…โ“†โ“‡โ“ˆโ“‰โ“Šโ“‹โ“Œโ“โ“Žโ“'; $this->assertEquals($expected, $result); $input = array(9424, 9425, 9426, 9427, 9428, 9429, 9430, 9431, 9432, 9433, 9434, 9435, 9436, 9437, 9438, 9439, 9440, 9441, 9442, 9443, 9444, 9445, 9446, 9447, 9448, 9449); $result = Multibyte::ascii($input); $expected = 'โ“โ“‘โ“’โ““โ“”โ“•โ“–โ“—โ“˜โ“™โ“šโ“›โ“œโ“โ“žโ“Ÿโ“ โ“กโ“ขโ“ฃโ“คโ“ฅโ“ฆโ“งโ“จโ“ฉ'; $this->assertEquals($expected, $result); $input = array(11264, 11265, 11266, 11267, 11268, 11269, 11270, 11271, 11272, 11273, 11274, 11275, 11276, 11277, 11278, 11279, 11280, 11281, 11282, 11283, 11284, 11285, 11286, 11287, 11288, 11289, 11290, 11291, 11292, 11293, 11294, 11295, 11296, 11297, 11298, 11299, 11300, 11301, 11302, 11303, 11304, 11305, 11306, 11307, 11308, 11309, 11310); $result = Multibyte::ascii($input); $expected = 'โฐ€โฐโฐ‚โฐƒโฐ„โฐ…โฐ†โฐ‡โฐˆโฐ‰โฐŠโฐ‹โฐŒโฐโฐŽโฐโฐโฐ‘โฐ’โฐ“โฐ”โฐ•โฐ–โฐ—โฐ˜โฐ™โฐšโฐ›โฐœโฐโฐžโฐŸโฐ โฐกโฐขโฐฃโฐคโฐฅโฐฆโฐงโฐจโฐฉโฐชโฐซโฐฌโฐญโฐฎ'; $this->assertEquals($expected, $result); $input = array(11312, 11313, 11314, 11315, 11316, 11317, 11318, 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326, 11327, 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, 11339, 11340, 11341, 11342, 11343, 11344, 11345, 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358); $result = Multibyte::ascii($input); $expected = 'โฐฐโฐฑโฐฒโฐณโฐดโฐตโฐถโฐทโฐธโฐนโฐบโฐปโฐผโฐฝโฐพโฐฟโฑ€โฑโฑ‚โฑƒโฑ„โฑ…โฑ†โฑ‡โฑˆโฑ‰โฑŠโฑ‹โฑŒโฑโฑŽโฑโฑโฑ‘โฑ’โฑ“โฑ”โฑ•โฑ–โฑ—โฑ˜โฑ™โฑšโฑ›โฑœโฑโฑž'; $this->assertEquals($expected, $result); $input = array(11392, 11394, 11396, 11398, 11400, 11402, 11404, 11406, 11408, 11410, 11412, 11414, 11416, 11418, 11420, 11422, 11424, 11426, 11428, 11430, 11432, 11434, 11436, 11438, 11440, 11442, 11444, 11446, 11448, 11450, 11452, 11454, 11456, 11458, 11460, 11462, 11464, 11466, 11468, 11470, 11472, 11474, 11476, 11478, 11480, 11482, 11484, 11486, 11488, 11490); $result = Multibyte::ascii($input); $expected = 'โฒ€โฒ‚โฒ„โฒ†โฒˆโฒŠโฒŒโฒŽโฒโฒ’โฒ”โฒ–โฒ˜โฒšโฒœโฒžโฒ โฒขโฒคโฒฆโฒจโฒชโฒฌโฒฎโฒฐโฒฒโฒดโฒถโฒธโฒบโฒผโฒพโณ€โณ‚โณ„โณ†โณˆโณŠโณŒโณŽโณโณ’โณ”โณ–โณ˜โณšโณœโณžโณ โณข'; $this->assertEquals($expected, $result); $input = array(11393, 11395, 11397, 11399, 11401, 11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419, 11421, 11423, 11425, 11427, 11429, 11431, 11433, 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449, 11451, 11453, 11455, 11457, 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473, 11475, 11477, 11479, 11481, 11483, 11485, 11487, 11489, 11491); $result = Multibyte::ascii($input); $expected = 'โฒโฒƒโฒ…โฒ‡โฒ‰โฒ‹โฒโฒโฒ‘โฒ“โฒ•โฒ—โฒ™โฒ›โฒโฒŸโฒกโฒฃโฒฅโฒงโฒฉโฒซโฒญโฒฏโฒฑโฒณโฒตโฒทโฒนโฒปโฒฝโฒฟโณโณƒโณ…โณ‡โณ‰โณ‹โณโณโณ‘โณ“โณ•โณ—โณ™โณ›โณโณŸโณกโณฃ'; $this->assertEquals($expected, $result); $input = array(64256, 64257, 64258, 64259, 64260, 64261, 64262, 64275, 64276, 64277, 64278, 64279); $result = Multibyte::ascii($input); $expected = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $this->assertEquals($expected, $result); } /** * testUsingMbStripos method * * @return void */ public function testUsingMbStripos() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'f'; $result = mb_stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'f'; $result = mb_stripos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'รฅ'; $result = mb_stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'รฅ'; $result = mb_stripos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = mb_stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = mb_stripos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'f'; $result = mb_stripos($string, $find); $expected = 37; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ฮœ'; $result = mb_stripos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'ร‰'; $result = mb_stripos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_stripos($string, $find); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_stripos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_stripos($string, $find, 40); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ฦฆ'; $result = mb_stripos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ั—'; $result = mb_stripos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_stripos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_stripos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_stripos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_stripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_stripos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_stripos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_stripos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_stripos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_stripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = mb_stripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_stripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_stripos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_stripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล'; $result = mb_stripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'O'; $result = mb_stripos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_stripos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'N'; $result = mb_stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ†'; $result = mb_stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลฝ'; $result = mb_stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_stripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_stripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'Dลฝ'; $result = mb_stripos($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testMultibyteStripos method * * @return void */ public function testMultibyteStripos() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'f'; $result = Multibyte::stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'f'; $result = Multibyte::stripos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'รฅ'; $result = Multibyte::stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'รฅ'; $result = Multibyte::stripos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = Multibyte::stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = Multibyte::stripos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'f'; $result = Multibyte::stripos($string, $find); $expected = 37; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ฮœ'; $result = Multibyte::stripos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'ร‰'; $result = Multibyte::stripos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::stripos($string, $find); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::stripos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::stripos($string, $find, 40); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ฦฆ'; $result = Multibyte::stripos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ั—'; $result = Multibyte::stripos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::stripos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::stripos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::stripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::stripos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::stripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::stripos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::stripos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::stripos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::stripos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::stripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = Multibyte::stripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::stripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::stripos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::stripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล'; $result = Multibyte::stripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'O'; $result = Multibyte::stripos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::stripos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'N'; $result = Multibyte::stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ†'; $result = Multibyte::stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลฝ'; $result = Multibyte::stripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::stripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::stripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'Dลฝ'; $result = Multibyte::stripos($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testUsingMbStristr method * * @return void */ public function testUsingMbStristr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'f'; $result = mb_stristr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'f'; $result = mb_stristr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'รฅ'; $result = mb_stristr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'รฅ'; $result = mb_stristr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = mb_stristr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = mb_stristr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'f'; $result = mb_stristr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'f'; $result = mb_stristr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ฮœ'; $result = mb_stristr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ฮœ'; $result = mb_stristr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รพ'; $result = mb_stristr($string, $find); $expected = 'รžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รพ'; $result = mb_stristr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœร'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_stristr($string, $find); $expected = 'ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_stristr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_stristr($string, $find); $expected = 'ฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = mb_stristr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦท'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ฦฆ'; $result = mb_stristr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ฦฆ'; $result = mb_stristr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ั—'; $result = mb_stristr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ั—'; $result = mb_stristr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_stristr($string, $find); $expected = 'ะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_stristr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_stristr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_stristr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_stristr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_stristr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_stristr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_stristr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_stristr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_stristr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_stristr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_stristr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_stristr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_stristr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_stristr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_stristr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = mb_stristr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = mb_stristr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_stristr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_stristr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_stristr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_stristr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล'; $result = mb_stristr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล'; $result = mb_stristr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ฤบฤผ'; $result = mb_stristr($string, $find, true); $expected = 'ฤคฤ“'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'O'; $result = mb_stristr($string, $find); $expected = 'o, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'O'; $result = mb_stristr($string, $find, true); $expected = 'Hell'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_stristr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_stristr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_stristr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_stristr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_stristr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_stristr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'N'; $result = mb_stristr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'N'; $result = mb_stristr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ†'; $result = mb_stristr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ†'; $result = mb_stristr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลฝ'; $result = mb_stristr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลฝ'; $result = mb_stristr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_stristr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_stristr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_stristr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_stristr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ไบŒๅ‘จ'; $result = mb_stristr($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testMultibyteStristr method * * @return void */ public function testMultibyteStristr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'f'; $result = Multibyte::stristr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'f'; $result = Multibyte::stristr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'รฅ'; $result = Multibyte::stristr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'รฅ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = Multibyte::stristr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤ‹'; $result = Multibyte::stristr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'f'; $result = Multibyte::stristr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'f'; $result = Multibyte::stristr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ฮœ'; $result = Multibyte::stristr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ฮœ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รพ'; $result = Multibyte::stristr($string, $find); $expected = 'รžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รพ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœร'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::stristr($string, $find); $expected = 'ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::stristr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::stristr($string, $find); $expected = 'ฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦท'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ฦฆ'; $result = Multibyte::stristr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ฦฆ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ั—'; $result = Multibyte::stristr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ั—'; $result = Multibyte::stristr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::stristr($string, $find); $expected = 'ะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::stristr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::stristr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::stristr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::stristr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::stristr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::stristr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::stristr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::stristr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::stristr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::stristr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::stristr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::stristr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::stristr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::stristr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::stristr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = Multibyte::stristr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = Multibyte::stristr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::stristr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::stristr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::stristr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::stristr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล'; $result = Multibyte::stristr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล'; $result = Multibyte::stristr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ฤบฤผ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ฤคฤ“'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'O'; $result = Multibyte::stristr($string, $find); $expected = 'o, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'O'; $result = Multibyte::stristr($string, $find, true); $expected = 'Hell'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::stristr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::stristr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::stristr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::stristr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::stristr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::stristr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'N'; $result = Multibyte::stristr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'N'; $result = Multibyte::stristr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ†'; $result = Multibyte::stristr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ†'; $result = Multibyte::stristr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลฝ'; $result = Multibyte::stristr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลฝ'; $result = Multibyte::stristr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::stristr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::stristr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::stristr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ไบŒๅ‘จ'; $result = Multibyte::stristr($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testUsingMbStrlen method * * @return void */ public function testUsingMbStrlen() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $result = mb_strlen($string); $expected = 36; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $result = mb_strlen($string); $expected = 30; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $result = mb_strlen($string); $expected = 61; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = mb_strlen($string); $expected = 94; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $result = mb_strlen($string); $expected = 40; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $result = mb_strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $result = mb_strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $result = mb_strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $result = mb_strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $result = mb_strlen($string); $expected = 28; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $result = mb_strlen($string); $expected = 49; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $result = mb_strlen($string); $expected = 15; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $result = mb_strlen($string); $expected = 47; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $result = mb_strlen($string); $expected = 96; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $result = mb_strlen($string); $expected = 59; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $result = mb_strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $result = mb_strlen($string); $expected = 65; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $result = mb_strlen($string); $expected = 76; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $result = mb_strlen($string); $expected = 26; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $result = mb_strlen($string); $expected = 24; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $result = mb_strlen($string); $expected = 38; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = mb_strlen($string); $expected = 13; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $result = mb_strlen($string); $expected = 13; $this->assertEquals($expected, $result); $string = 'ฤini'; $result = mb_strlen($string); $expected = 4; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $result = mb_strlen($string); $expected = 4; $this->assertEquals($expected, $result); $string = 'drลพavni'; $result = mb_strlen($string); $expected = 7; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $result = mb_strlen($string); $expected = 7; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = mb_strlen($string); $expected = 6; $this->assertEquals($expected, $result); } /** * testMultibyteStrlen method * * @return void */ public function testMultibyteStrlen() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $result = Multibyte::strlen($string); $expected = 36; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $result = Multibyte::strlen($string); $expected = 30; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $result = Multibyte::strlen($string); $expected = 61; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = Multibyte::strlen($string); $expected = 94; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $result = Multibyte::strlen($string); $expected = 40; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $result = Multibyte::strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $result = Multibyte::strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $result = Multibyte::strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $result = Multibyte::strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $result = Multibyte::strlen($string); $expected = 28; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $result = Multibyte::strlen($string); $expected = 49; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $result = Multibyte::strlen($string); $expected = 15; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $result = Multibyte::strlen($string); $expected = 47; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $result = Multibyte::strlen($string); $expected = 96; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $result = Multibyte::strlen($string); $expected = 59; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $result = Multibyte::strlen($string); $expected = 100; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $result = Multibyte::strlen($string); $expected = 65; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $result = Multibyte::strlen($string); $expected = 76; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $result = Multibyte::strlen($string); $expected = 26; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $result = Multibyte::strlen($string); $expected = 24; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $result = Multibyte::strlen($string); $expected = 38; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = Multibyte::strlen($string); $expected = 13; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $result = Multibyte::strlen($string); $expected = 13; $this->assertEquals($expected, $result); $string = 'ฤini'; $result = Multibyte::strlen($string); $expected = 4; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $result = Multibyte::strlen($string); $expected = 4; $this->assertEquals($expected, $result); $string = 'drลพavni'; $result = Multibyte::strlen($string); $expected = 7; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $result = Multibyte::strlen($string); $expected = 7; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = Multibyte::strlen($string); $expected = 6; $this->assertEquals($expected, $result); } /** * testUsingMbStrpos method * * @return void */ public function testUsingMbStrpos() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strpos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strpos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strpos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strpos($string, $find); $expected = 37; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strpos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รฉ'; $result = mb_strpos($string, $find); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strpos($string, $find); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_strpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = mb_strpos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strpos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strpos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = mb_strpos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strpos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strpos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strpos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strpos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strpos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strpos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘ล™'; $result = mb_strpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strpos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strpos($string, $find, 5); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ไธ€ๅ‘จ'; $result = mb_strpos($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testMultibyteStrpos method * * @return void */ public function testMultibyteStrpos() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strpos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strpos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strpos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strpos($string, $find); $expected = 37; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strpos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รฉ'; $result = Multibyte::strpos($string, $find); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strpos($string, $find); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::strpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = Multibyte::strpos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strpos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strpos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = Multibyte::strpos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strpos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strpos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strpos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strpos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strpos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strpos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘ล™'; $result = Multibyte::strpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strpos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strpos($string, $find, 5); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ไธ€ๅ‘จ'; $result = Multibyte::strpos($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testUsingMbStrrchr method * * @return void */ public function testUsingMbStrrchr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strrchr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strrchr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strrchr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strrchr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strrchr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strrchr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strrchr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strrchr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strrchr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strrchr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = mb_strrchr($string, $find); $expected = 'รžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = mb_strrchr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœร'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strrchr($string, $find); $expected = 'ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strrchr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_strrchr($string, $find); $expected = 'ฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = mb_strrchr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦท'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strrchr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strrchr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strrchr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strrchr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strrchr($string, $find); $expected = 'ะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strrchr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strrchr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strrchr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strrchr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strrchr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strrchr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strrchr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strrchr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strrchr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strrchr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strrchr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strrchr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strrchr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strrchr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strrchr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strrchr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strrchr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strrchr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strrchr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strrchr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strrchr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strrchr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strrchr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strrchr($string, $find); $expected = 'orld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strrchr($string, $find, true); $expected = 'Hello, W'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_strrchr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_strrchr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_strrchr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_strrchr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_strrchr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_strrchr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strrchr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strrchr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strrchr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strrchr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strrchr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strrchr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strrchr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strrchr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strrchr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strrchr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ้พ'; $result = mb_strrchr($string, $find, true); $expected = false; $this->assertEquals($expected, $result); } /** * testMultibyteStrrchr method * * @return void */ public function testMultibyteStrrchr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strrchr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strrchr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strrchr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strrchr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strrchr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strrchr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = Multibyte::strrchr($string, $find); $expected = 'รžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœร'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strrchr($string, $find); $expected = 'ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::strrchr($string, $find); $expected = 'ฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦท'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strrchr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strrchr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strrchr($string, $find); $expected = 'ะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strrchr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strrchr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strrchr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strrchr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strrchr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strrchr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strrchr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strrchr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strrchr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strrchr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strrchr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strrchr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strrchr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strrchr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strrchr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strrchr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strrchr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strrchr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strrchr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strrchr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strrchr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strrchr($string, $find); $expected = 'orld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strrchr($string, $find, true); $expected = 'Hello, W'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::strrchr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::strrchr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::strrchr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::strrchr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::strrchr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::strrchr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strrchr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strrchr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strrchr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strrchr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strrchr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strrchr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strrchr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strrchr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ้พ'; $result = Multibyte::strrchr($string, $find, true); $expected = false; $this->assertEquals($expected, $result); } /** * testUsingMbStrrichr method * * @return void */ public function testUsingMbStrrichr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strrichr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strrichr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strrichr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strrichr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strrichr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strrichr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strrichr($string, $find); $expected = 'fghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strrichr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcde'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strrichr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strrichr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = mb_strrichr($string, $find); $expected = 'รพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = mb_strrichr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strrichr($string, $find); $expected = 'ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strrichr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_strrichr($string, $find); $expected = 'ฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = mb_strrichr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strrichr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strrichr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strrichr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strrichr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strrichr($string, $find); $expected = 'ั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟ'; $find = 'ะ '; $result = mb_strrichr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strrichr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strrichr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strrichr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strrichr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strrichr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strrichr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strrichr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strrichr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strrichr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strrichr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strrichr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strrichr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strrichr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strrichr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strrichr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strrichr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strrichr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strrichr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strrichr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strrichr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strrichr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strrichr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strrichr($string, $find); $expected = 'orld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strrichr($string, $find, true); $expected = 'Hello, W'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_strrichr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_strrichr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_strrichr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_strrichr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_strrichr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_strrichr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strrichr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strrichr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strrichr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strrichr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strrichr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strrichr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strrichr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strrichr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strrichr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strrichr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '็™พ่ฎพ'; $result = mb_strrichr($string, $find, true); $expected = false; $this->assertEquals($expected, $result); } /** * testMultibyteStrrichr method * * @return void */ public function testMultibyteStrrichr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strrichr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strrichr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strrichr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strrichr($string, $find); $expected = 'fghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strrichr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcde'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strrichr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = Multibyte::strrichr($string, $find); $expected = 'รพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strrichr($string, $find); $expected = 'ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::strrichr($string, $find); $expected = 'ฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strrichr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strrichr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strrichr($string, $find); $expected = 'ั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟ'; $find = 'ะ '; $result = Multibyte::strrichr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strrichr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strrichr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strrichr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strrichr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strrichr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strrichr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strrichr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strrichr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strrichr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strrichr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strrichr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strrichr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strrichr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strrichr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strrichr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strrichr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strrichr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strrichr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strrichr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strrichr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strrichr($string, $find); $expected = 'orld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strrichr($string, $find, true); $expected = 'Hello, W'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::strrichr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::strrichr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::strrichr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::strrichr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::strrichr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::strrichr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strrichr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strrichr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strrichr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strrichr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strrichr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strrichr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strrichr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strrichr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '็™พ่ฎพ'; $result = Multibyte::strrichr($string, $find, true); $expected = false; $this->assertEquals($expected, $result); } /** * testUsingMbStrripos method * * @return void */ public function testUsingMbStrripos() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strripos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strripos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร“ร”'; $result = mb_strripos($string, $find); $expected = 19; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strripos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strripos($string, $find); $expected = 69; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strripos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รฉ'; $result = mb_strripos($string, $find); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strripos($string, $find); $expected = 25; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_strripos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = mb_strripos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strripos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strripos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strripos($string, $find); $expected = 36; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = mb_strripos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strripos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strripos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strripos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strripos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strripos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ๏ฝ'; $result = mb_strripos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strripos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strripos($string, $find, 5); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'dลพ'; $result = mb_strripos($string, $find); $this->assertFalse($result); } /** * testMultibyteStrripos method * * @return void */ public function testMultibyteStrripos() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strripos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strripos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร“ร”'; $result = Multibyte::strripos($string, $find); $expected = 19; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strripos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strripos($string, $find); $expected = 69; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strripos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รฉ'; $result = Multibyte::strripos($string, $find); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strripos($string, $find); $expected = 25; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::strripos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = Multibyte::strripos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strripos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strripos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strripos($string, $find); $expected = 36; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = Multibyte::strripos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strripos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strripos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strripos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strripos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strripos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strripos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strripos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ๏ฝ'; $result = Multibyte::strripos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strripos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strripos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strripos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strripos($string, $find, 5); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strripos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strripos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'dลพ'; $result = Multibyte::strripos($string, $find); $expected = 0; $this->assertEquals($expected, $result); } /** * testUsingMbStrrpos method * * @return void */ public function testUsingMbStrrpos() { $this->skipIf(extension_loaded('mbstring') && version_compare(PHP_VERSION, '5.2.0', '<'), 'PHP version does not support $offset parameter in mb_strrpos().'); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strrpos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร™รš'; $result = mb_strrpos($string, $find); $expected = 25; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strrpos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strrpos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strrpos($string, $find); $expected = 37; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strrpos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รฉ'; $result = mb_strrpos($string, $find); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strrpos($string, $find); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_strrpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = mb_strrpos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strrpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strrpos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strrpos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = mb_strrpos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strrpos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strrpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strrpos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strrpos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strrpos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strrpos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strrpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strrpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ๏ฝ'; $result = mb_strrpos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strrpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strrpos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strrpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strrpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strrpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strrpos($string, $find, 5); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strrpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strrpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'H'; $result = mb_strrpos($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testMultibyteStrrpos method * * @return void */ public function testMultibyteStrrpos() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strrpos($string, $find, 6); $expected = 17; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strrpos($string, $find, 6); $expected = 24; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรž'; $find = 'ร™รš'; $result = Multibyte::strrpos($string, $find); $expected = 25; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strrpos($string, $find, 6); $expected = 32; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strrpos($string, $find); $expected = 37; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strrpos($string, $find); $expected = 20; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รฉ'; $result = Multibyte::strrpos($string, $find); $expected = 32; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strrpos($string, $find); $expected = 24; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::strrpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = Multibyte::strrpos($string, $find); $expected = 40; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strrpos($string, $find); $expected = 39; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strrpos($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strrpos($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = Multibyte::strrpos($string, $find, 5); $expected = 36; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strrpos($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strrpos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strrpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strrpos($string, $find); $expected = 31; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strrpos($string, $find); $expected = 26; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strrpos($string, $find); $expected = 46; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strrpos($string, $find); $expected = 45; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strrpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strrpos($string, $find); $expected = 10; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ๏ฝ'; $result = Multibyte::strrpos($string, $find); $expected = 15; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strrpos($string, $find); $expected = 16; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strrpos($string, $find); $expected = 17; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strrpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strrpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strrpos($string, $find); $expected = 8; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strrpos($string, $find, 5); $expected = 8; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strrpos($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strrpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strrpos($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'H'; $result = Multibyte::strrpos($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testUsingMbStrstr method * * @return void */ public function testUsingMbStrstr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strstr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_strstr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strstr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = mb_strstr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strstr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_strstr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strstr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = mb_strstr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strstr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = mb_strstr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = mb_strstr($string, $find); $expected = 'รžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = mb_strstr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœร'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strstr($string, $find); $expected = 'ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = mb_strstr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_strstr($string, $find); $expected = 'ฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = mb_strstr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦท'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strstr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = mb_strstr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strstr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_strstr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strstr($string, $find); $expected = 'ะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_strstr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strstr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = mb_strstr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strstr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_strstr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strstr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_strstr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strstr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_strstr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strstr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = mb_strstr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strstr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = mb_strstr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strstr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_strstr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strstr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_strstr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = mb_strstr($string, $find); $expected = false; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strstr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_strstr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strstr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_strstr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strstr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_strstr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ฤบฤผ'; $result = mb_strstr($string, $find, true); $expected = 'ฤคฤ“'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strstr($string, $find); $expected = 'o, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_strstr($string, $find, true); $expected = 'Hell'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_strstr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = mb_strstr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_strstr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = mb_strstr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_strstr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = mb_strstr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strstr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_strstr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strstr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_strstr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strstr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_strstr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strstr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_strstr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strstr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_strstr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ไบŒๅ‘จ'; $result = mb_strstr($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testMultibyteStrstr method * * @return void */ public function testMultibyteStrstr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strstr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::strstr($string, $find, true); $expected = 'ABCDE'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strstr($string, $find); $expected = 'ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $find = 'ร…'; $result = Multibyte::strstr($string, $find, true); $expected = 'ร€รร‚รƒร„'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strstr($string, $find); $expected = 'ฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::strstr($string, $find, true); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆ'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strstr($string, $find); $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $find = 'F'; $result = Multibyte::strstr($string, $find, true); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strstr($string, $find); $expected = 'ยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $find = 'ยต'; $result = Multibyte::strstr($string, $find, true); $expected = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยด'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = Multibyte::strstr($string, $find); $expected = 'รžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $find = 'รž'; $result = Multibyte::strstr($string, $find, true); $expected = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœร'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strstr($string, $find); $expected = 'ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ล…'; $result = Multibyte::strstr($string, $find, true); $expected = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::strstr($string, $find); $expected = 'ฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $find = 'ฦธ'; $result = Multibyte::strstr($string, $find, true); $expected = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦท'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strstr($string, $find); $expected = 'ส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $find = 'ส€'; $result = Multibyte::strstr($string, $find, true); $expected = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strstr($string, $find); $expected = 'ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::strstr($string, $find, true); $expected = 'ะ€ะะ‚ะƒะ„ะ…ะ†'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strstr($string, $find); $expected = 'ะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::strstr($string, $find, true); $expected = 'ะœะะžะŸ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strstr($string, $find); $expected = 'ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::strstr($string, $find, true); $expected = 'ูู‚ูƒู„ู…'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strstr($string, $find); $expected = 'โœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::strstr($string, $find, true); $expected = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strstr($string, $find); $expected = 'โบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::strstr($string, $find, true); $expected = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strstr($string, $find); $expected = 'โฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::strstr($string, $find, true); $expected = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strstr($string, $find); $expected = '๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆป'; $result = Multibyte::strstr($string, $find, true); $expected = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strstr($string, $find); $expected = '๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž'; $result = Multibyte::strstr($string, $find, true); $expected = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strstr($string, $find); $expected = '๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::strstr($string, $find, true); $expected = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strstr($string, $find); $expected = '๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::strstr($string, $find, true); $expected = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ผซ'; $result = Multibyte::strstr($string, $find); $expected = false; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strstr($string, $find); $expected = '๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::strstr($string, $find, true); $expected = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strstr($string, $find); $expected = '๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::strstr($string, $find, true); $expected = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strstr($string, $find); $expected = 'ล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::strstr($string, $find, true); $expected = 'ฤคฤ“ฤบฤผล, ลด'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ฤบฤผ'; $result = Multibyte::strstr($string, $find, true); $expected = 'ฤคฤ“'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strstr($string, $find); $expected = 'o, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::strstr($string, $find, true); $expected = 'Hell'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::strstr($string, $find); $expected = 'World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'Wo'; $result = Multibyte::strstr($string, $find, true); $expected = 'Hello, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::strstr($string, $find); $expected = 'llo, World!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'll'; $result = Multibyte::strstr($string, $find, true); $expected = 'He'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::strstr($string, $find); $expected = 'rld!'; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rld'; $result = Multibyte::strstr($string, $find, true); $expected = 'Hello, Wo'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strstr($string, $find); $expected = 'ni'; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::strstr($string, $find, true); $expected = 'ฤi'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strstr($string, $find); $expected = 'ฤ‡i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::strstr($string, $find, true); $expected = 'mo'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strstr($string, $find); $expected = 'ลพavni'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::strstr($string, $find, true); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strstr($string, $find); $expected = '่ฎพไธบ้ฆ–้กต'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::strstr($string, $find, true); $expected = 'ๆŠŠ็™พๅบฆ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strstr($string, $find); $expected = 'ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::strstr($string, $find, true); $expected = 'ไธ€ไบŒไธ‰'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ไบŒๅ‘จ'; $result = Multibyte::strstr($string, $find); $expected = false; $this->assertEquals($expected, $result); } /** * testUsingMbStrtolower method * * @return void */ public function testUsingMbStrtolower() { $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; $result = mb_strtolower($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $result = mb_strtolower($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $this->assertEquals($expected, $result); $string = 'ร€'; $result = mb_strtolower($string); $expected = 'ร '; $this->assertEquals($expected, $result); $string = 'ร'; $result = mb_strtolower($string); $expected = 'รก'; $this->assertEquals($expected, $result); $string = 'ร‚'; $result = mb_strtolower($string); $expected = 'รข'; $this->assertEquals($expected, $result); $string = 'รƒ'; $result = mb_strtolower($string); $expected = 'รฃ'; $this->assertEquals($expected, $result); $string = 'ร„'; $result = mb_strtolower($string); $expected = 'รค'; $this->assertEquals($expected, $result); $string = 'ร…'; $result = mb_strtolower($string); $expected = 'รฅ'; $this->assertEquals($expected, $result); $string = 'ร†'; $result = mb_strtolower($string); $expected = 'รฆ'; $this->assertEquals($expected, $result); $string = 'ร‡'; $result = mb_strtolower($string); $expected = 'รง'; $this->assertEquals($expected, $result); $string = 'รˆ'; $result = mb_strtolower($string); $expected = 'รจ'; $this->assertEquals($expected, $result); $string = 'ร‰'; $result = mb_strtolower($string); $expected = 'รฉ'; $this->assertEquals($expected, $result); $string = 'รŠ'; $result = mb_strtolower($string); $expected = 'รช'; $this->assertEquals($expected, $result); $string = 'ร‹'; $result = mb_strtolower($string); $expected = 'รซ'; $this->assertEquals($expected, $result); $string = 'รŒ'; $result = mb_strtolower($string); $expected = 'รฌ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = mb_strtolower($string); $expected = 'รญ'; $this->assertEquals($expected, $result); $string = 'รŽ'; $result = mb_strtolower($string); $expected = 'รฎ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = mb_strtolower($string); $expected = 'รฏ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = mb_strtolower($string); $expected = 'รฐ'; $this->assertEquals($expected, $result); $string = 'ร‘'; $result = mb_strtolower($string); $expected = 'รฑ'; $this->assertEquals($expected, $result); $string = 'ร’'; $result = mb_strtolower($string); $expected = 'รฒ'; $this->assertEquals($expected, $result); $string = 'ร“'; $result = mb_strtolower($string); $expected = 'รณ'; $this->assertEquals($expected, $result); $string = 'ร”'; $result = mb_strtolower($string); $expected = 'รด'; $this->assertEquals($expected, $result); $string = 'ร•'; $result = mb_strtolower($string); $expected = 'รต'; $this->assertEquals($expected, $result); $string = 'ร–'; $result = mb_strtolower($string); $expected = 'รถ'; $this->assertEquals($expected, $result); $string = 'ร˜'; $result = mb_strtolower($string); $expected = 'รธ'; $this->assertEquals($expected, $result); $string = 'ร™'; $result = mb_strtolower($string); $expected = 'รน'; $this->assertEquals($expected, $result); $string = 'รš'; $result = mb_strtolower($string); $expected = 'รบ'; $this->assertEquals($expected, $result); $string = 'ร›'; $result = mb_strtolower($string); $expected = 'รป'; $this->assertEquals($expected, $result); $string = 'รœ'; $result = mb_strtolower($string); $expected = 'รผ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = mb_strtolower($string); $expected = 'รฝ'; $this->assertEquals($expected, $result); $string = 'รž'; $result = mb_strtolower($string); $expected = 'รพ'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $result = mb_strtolower($string); $expected = 'ร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรธรนรบรปรผรฝรพ'; $this->assertEquals($expected, $result); $string = 'ฤ€'; $result = mb_strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤ‚'; $result = mb_strtolower($string); $expected = 'ฤƒ'; $this->assertEquals($expected, $result); $string = 'ฤ„'; $result = mb_strtolower($string); $expected = 'ฤ…'; $this->assertEquals($expected, $result); $string = 'ฤ†'; $result = mb_strtolower($string); $expected = 'ฤ‡'; $this->assertEquals($expected, $result); $string = 'ฤˆ'; $result = mb_strtolower($string); $expected = 'ฤ‰'; $this->assertEquals($expected, $result); $string = 'ฤŠ'; $result = mb_strtolower($string); $expected = 'ฤ‹'; $this->assertEquals($expected, $result); $string = 'ฤŒ'; $result = mb_strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤŽ'; $result = mb_strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = mb_strtolower($string); $expected = 'ฤ‘'; $this->assertEquals($expected, $result); $string = 'ฤ’'; $result = mb_strtolower($string); $expected = 'ฤ“'; $this->assertEquals($expected, $result); $string = 'ฤ”'; $result = mb_strtolower($string); $expected = 'ฤ•'; $this->assertEquals($expected, $result); $string = 'ฤ–'; $result = mb_strtolower($string); $expected = 'ฤ—'; $this->assertEquals($expected, $result); $string = 'ฤ˜'; $result = mb_strtolower($string); $expected = 'ฤ™'; $this->assertEquals($expected, $result); $string = 'ฤš'; $result = mb_strtolower($string); $expected = 'ฤ›'; $this->assertEquals($expected, $result); $string = 'ฤœ'; $result = mb_strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤž'; $result = mb_strtolower($string); $expected = 'ฤŸ'; $this->assertEquals($expected, $result); $string = 'ฤ '; $result = mb_strtolower($string); $expected = 'ฤก'; $this->assertEquals($expected, $result); $string = 'ฤข'; $result = mb_strtolower($string); $expected = 'ฤฃ'; $this->assertEquals($expected, $result); $string = 'ฤค'; $result = mb_strtolower($string); $expected = 'ฤฅ'; $this->assertEquals($expected, $result); $string = 'ฤฆ'; $result = mb_strtolower($string); $expected = 'ฤง'; $this->assertEquals($expected, $result); $string = 'ฤจ'; $result = mb_strtolower($string); $expected = 'ฤฉ'; $this->assertEquals($expected, $result); $string = 'ฤช'; $result = mb_strtolower($string); $expected = 'ฤซ'; $this->assertEquals($expected, $result); $string = 'ฤฌ'; $result = mb_strtolower($string); $expected = 'ฤญ'; $this->assertEquals($expected, $result); $string = 'ฤฎ'; $result = mb_strtolower($string); $expected = 'ฤฏ'; $this->assertEquals($expected, $result); $string = 'ฤฒ'; $result = mb_strtolower($string); $expected = 'ฤณ'; $this->assertEquals($expected, $result); $string = 'ฤด'; $result = mb_strtolower($string); $expected = 'ฤต'; $this->assertEquals($expected, $result); $string = 'ฤถ'; $result = mb_strtolower($string); $expected = 'ฤท'; $this->assertEquals($expected, $result); $string = 'ฤน'; $result = mb_strtolower($string); $expected = 'ฤบ'; $this->assertEquals($expected, $result); $string = 'ฤป'; $result = mb_strtolower($string); $expected = 'ฤผ'; $this->assertEquals($expected, $result); $string = 'ฤฝ'; $result = mb_strtolower($string); $expected = 'ฤพ'; $this->assertEquals($expected, $result); $string = 'ฤฟ'; $result = mb_strtolower($string); $expected = 'ล€'; $this->assertEquals($expected, $result); $string = 'ล'; $result = mb_strtolower($string); $expected = 'ล‚'; $this->assertEquals($expected, $result); $string = 'ลƒ'; $result = mb_strtolower($string); $expected = 'ล„'; $this->assertEquals($expected, $result); $string = 'ล…'; $result = mb_strtolower($string); $expected = 'ล†'; $this->assertEquals($expected, $result); $string = 'ล‡'; $result = mb_strtolower($string); $expected = 'ลˆ'; $this->assertEquals($expected, $result); $string = 'ลŠ'; $result = mb_strtolower($string); $expected = 'ล‹'; $this->assertEquals($expected, $result); $string = 'ลŒ'; $result = mb_strtolower($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ลŽ'; $result = mb_strtolower($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ล'; $result = mb_strtolower($string); $expected = 'ล‘'; $this->assertEquals($expected, $result); $string = 'ล’'; $result = mb_strtolower($string); $expected = 'ล“'; $this->assertEquals($expected, $result); $string = 'ล”'; $result = mb_strtolower($string); $expected = 'ล•'; $this->assertEquals($expected, $result); $string = 'ล–'; $result = mb_strtolower($string); $expected = 'ล—'; $this->assertEquals($expected, $result); $string = 'ล˜'; $result = mb_strtolower($string); $expected = 'ล™'; $this->assertEquals($expected, $result); $string = 'ลš'; $result = mb_strtolower($string); $expected = 'ล›'; $this->assertEquals($expected, $result); $string = 'ลœ'; $result = mb_strtolower($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ลž'; $result = mb_strtolower($string); $expected = 'ลŸ'; $this->assertEquals($expected, $result); $string = 'ล '; $result = mb_strtolower($string); $expected = 'ลก'; $this->assertEquals($expected, $result); $string = 'ลข'; $result = mb_strtolower($string); $expected = 'ลฃ'; $this->assertEquals($expected, $result); $string = 'ลค'; $result = mb_strtolower($string); $expected = 'ลฅ'; $this->assertEquals($expected, $result); $string = 'ลฆ'; $result = mb_strtolower($string); $expected = 'ลง'; $this->assertEquals($expected, $result); $string = 'ลจ'; $result = mb_strtolower($string); $expected = 'ลฉ'; $this->assertEquals($expected, $result); $string = 'ลช'; $result = mb_strtolower($string); $expected = 'ลซ'; $this->assertEquals($expected, $result); $string = 'ลฌ'; $result = mb_strtolower($string); $expected = 'ลญ'; $this->assertEquals($expected, $result); $string = 'ลฎ'; $result = mb_strtolower($string); $expected = 'ลฏ'; $this->assertEquals($expected, $result); $string = 'ลฐ'; $result = mb_strtolower($string); $expected = 'ลฑ'; $this->assertEquals($expected, $result); $string = 'ลฒ'; $result = mb_strtolower($string); $expected = 'ลณ'; $this->assertEquals($expected, $result); $string = 'ลด'; $result = mb_strtolower($string); $expected = 'ลต'; $this->assertEquals($expected, $result); $string = 'ลถ'; $result = mb_strtolower($string); $expected = 'ลท'; $this->assertEquals($expected, $result); $string = 'ลน'; $result = mb_strtolower($string); $expected = 'ลบ'; $this->assertEquals($expected, $result); $string = 'ลป'; $result = mb_strtolower($string); $expected = 'ลผ'; $this->assertEquals($expected, $result); $string = 'ลฝ'; $result = mb_strtolower($string); $expected = 'ลพ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $result = mb_strtolower($string); $expected = 'ฤฤƒฤ…ฤ‡ฤ‰ฤ‹ฤฤฤ‘ฤ“ฤ•ฤ—ฤ™ฤ›ฤฤŸฤกฤฃฤฅฤงฤฉฤซฤญฤฏฤณฤตฤทฤบฤผฤพล€ล‚ล„ล†ลˆล‹ลลล‘ล“ล•ล—ล™ล›ลลŸลกลฃลฅลงลฉลซลญลฏลฑลณลตลทลบลผลพ'; $this->assertEquals($expected, $result); $string = 'ฤคฤ’ฤนฤปลŽ, ลดลล˜ฤปฤŽ!'; $result = mb_strtolower($string); $expected = 'ฤฅฤ“ฤบฤผล, ลตล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤฅฤ“ฤบฤผล, ลตล‘ล™ฤผฤ!'; $result = mb_strtolower($string); $expected = 'ฤฅฤ“ฤบฤผล, ลตล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'แผˆฮ™'; $result = mb_strtolower($string); $expected = 'แผ€ฮน'; $this->assertEquals($expected, $result); $string = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $result = mb_strtolower($string); $expected = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $this->assertEquals($expected, $result); } /** * testMultibyteStrtolower method * * @return void */ public function testMultibyteStrtolower() { $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; $result = Multibyte::strtolower($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $result = Multibyte::strtolower($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $this->assertEquals($expected, $result); $string = 'ร€'; $result = Multibyte::strtolower($string); $expected = 'ร '; $this->assertEquals($expected, $result); $string = 'ร'; $result = Multibyte::strtolower($string); $expected = 'รก'; $this->assertEquals($expected, $result); $string = 'ร‚'; $result = Multibyte::strtolower($string); $expected = 'รข'; $this->assertEquals($expected, $result); $string = 'รƒ'; $result = Multibyte::strtolower($string); $expected = 'รฃ'; $this->assertEquals($expected, $result); $string = 'ร„'; $result = Multibyte::strtolower($string); $expected = 'รค'; $this->assertEquals($expected, $result); $string = 'ร…'; $result = Multibyte::strtolower($string); $expected = 'รฅ'; $this->assertEquals($expected, $result); $string = 'ร†'; $result = Multibyte::strtolower($string); $expected = 'รฆ'; $this->assertEquals($expected, $result); $string = 'ร‡'; $result = Multibyte::strtolower($string); $expected = 'รง'; $this->assertEquals($expected, $result); $string = 'รˆ'; $result = Multibyte::strtolower($string); $expected = 'รจ'; $this->assertEquals($expected, $result); $string = 'ร‰'; $result = Multibyte::strtolower($string); $expected = 'รฉ'; $this->assertEquals($expected, $result); $string = 'รŠ'; $result = Multibyte::strtolower($string); $expected = 'รช'; $this->assertEquals($expected, $result); $string = 'ร‹'; $result = Multibyte::strtolower($string); $expected = 'รซ'; $this->assertEquals($expected, $result); $string = 'รŒ'; $result = Multibyte::strtolower($string); $expected = 'รฌ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = Multibyte::strtolower($string); $expected = 'รญ'; $this->assertEquals($expected, $result); $string = 'รŽ'; $result = Multibyte::strtolower($string); $expected = 'รฎ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = Multibyte::strtolower($string); $expected = 'รฏ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = Multibyte::strtolower($string); $expected = 'รฐ'; $this->assertEquals($expected, $result); $string = 'ร‘'; $result = Multibyte::strtolower($string); $expected = 'รฑ'; $this->assertEquals($expected, $result); $string = 'ร’'; $result = Multibyte::strtolower($string); $expected = 'รฒ'; $this->assertEquals($expected, $result); $string = 'ร“'; $result = Multibyte::strtolower($string); $expected = 'รณ'; $this->assertEquals($expected, $result); $string = 'ร”'; $result = Multibyte::strtolower($string); $expected = 'รด'; $this->assertEquals($expected, $result); $string = 'ร•'; $result = Multibyte::strtolower($string); $expected = 'รต'; $this->assertEquals($expected, $result); $string = 'ร–'; $result = Multibyte::strtolower($string); $expected = 'รถ'; $this->assertEquals($expected, $result); $string = 'ร˜'; $result = Multibyte::strtolower($string); $expected = 'รธ'; $this->assertEquals($expected, $result); $string = 'ร™'; $result = Multibyte::strtolower($string); $expected = 'รน'; $this->assertEquals($expected, $result); $string = 'รš'; $result = Multibyte::strtolower($string); $expected = 'รบ'; $this->assertEquals($expected, $result); $string = 'ร›'; $result = Multibyte::strtolower($string); $expected = 'รป'; $this->assertEquals($expected, $result); $string = 'รœ'; $result = Multibyte::strtolower($string); $expected = 'รผ'; $this->assertEquals($expected, $result); $string = 'ร'; $result = Multibyte::strtolower($string); $expected = 'รฝ'; $this->assertEquals($expected, $result); $string = 'รž'; $result = Multibyte::strtolower($string); $expected = 'รพ'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $result = Multibyte::strtolower($string); $expected = 'ร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรธรนรบรปรผรฝรพ'; $this->assertEquals($expected, $result); $string = 'ฤ€'; $result = Multibyte::strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤ‚'; $result = Multibyte::strtolower($string); $expected = 'ฤƒ'; $this->assertEquals($expected, $result); $string = 'ฤ„'; $result = Multibyte::strtolower($string); $expected = 'ฤ…'; $this->assertEquals($expected, $result); $string = 'ฤ†'; $result = Multibyte::strtolower($string); $expected = 'ฤ‡'; $this->assertEquals($expected, $result); $string = 'ฤˆ'; $result = Multibyte::strtolower($string); $expected = 'ฤ‰'; $this->assertEquals($expected, $result); $string = 'ฤŠ'; $result = Multibyte::strtolower($string); $expected = 'ฤ‹'; $this->assertEquals($expected, $result); $string = 'ฤŒ'; $result = Multibyte::strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤŽ'; $result = Multibyte::strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = Multibyte::strtolower($string); $expected = 'ฤ‘'; $this->assertEquals($expected, $result); $string = 'ฤ’'; $result = Multibyte::strtolower($string); $expected = 'ฤ“'; $this->assertEquals($expected, $result); $string = 'ฤ”'; $result = Multibyte::strtolower($string); $expected = 'ฤ•'; $this->assertEquals($expected, $result); $string = 'ฤ–'; $result = Multibyte::strtolower($string); $expected = 'ฤ—'; $this->assertEquals($expected, $result); $string = 'ฤ˜'; $result = Multibyte::strtolower($string); $expected = 'ฤ™'; $this->assertEquals($expected, $result); $string = 'ฤš'; $result = Multibyte::strtolower($string); $expected = 'ฤ›'; $this->assertEquals($expected, $result); $string = 'ฤœ'; $result = Multibyte::strtolower($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤž'; $result = Multibyte::strtolower($string); $expected = 'ฤŸ'; $this->assertEquals($expected, $result); $string = 'ฤ '; $result = Multibyte::strtolower($string); $expected = 'ฤก'; $this->assertEquals($expected, $result); $string = 'ฤข'; $result = Multibyte::strtolower($string); $expected = 'ฤฃ'; $this->assertEquals($expected, $result); $string = 'ฤค'; $result = Multibyte::strtolower($string); $expected = 'ฤฅ'; $this->assertEquals($expected, $result); $string = 'ฤฆ'; $result = Multibyte::strtolower($string); $expected = 'ฤง'; $this->assertEquals($expected, $result); $string = 'ฤจ'; $result = Multibyte::strtolower($string); $expected = 'ฤฉ'; $this->assertEquals($expected, $result); $string = 'ฤช'; $result = Multibyte::strtolower($string); $expected = 'ฤซ'; $this->assertEquals($expected, $result); $string = 'ฤฌ'; $result = Multibyte::strtolower($string); $expected = 'ฤญ'; $this->assertEquals($expected, $result); $string = 'ฤฎ'; $result = Multibyte::strtolower($string); $expected = 'ฤฏ'; $this->assertEquals($expected, $result); $string = 'ฤฒ'; $result = Multibyte::strtolower($string); $expected = 'ฤณ'; $this->assertEquals($expected, $result); $string = 'ฤด'; $result = Multibyte::strtolower($string); $expected = 'ฤต'; $this->assertEquals($expected, $result); $string = 'ฤถ'; $result = Multibyte::strtolower($string); $expected = 'ฤท'; $this->assertEquals($expected, $result); $string = 'ฤน'; $result = Multibyte::strtolower($string); $expected = 'ฤบ'; $this->assertEquals($expected, $result); $string = 'ฤป'; $result = Multibyte::strtolower($string); $expected = 'ฤผ'; $this->assertEquals($expected, $result); $string = 'ฤฝ'; $result = Multibyte::strtolower($string); $expected = 'ฤพ'; $this->assertEquals($expected, $result); $string = 'ฤฟ'; $result = Multibyte::strtolower($string); $expected = 'ล€'; $this->assertEquals($expected, $result); $string = 'ล'; $result = Multibyte::strtolower($string); $expected = 'ล‚'; $this->assertEquals($expected, $result); $string = 'ลƒ'; $result = Multibyte::strtolower($string); $expected = 'ล„'; $this->assertEquals($expected, $result); $string = 'ล…'; $result = Multibyte::strtolower($string); $expected = 'ล†'; $this->assertEquals($expected, $result); $string = 'ล‡'; $result = Multibyte::strtolower($string); $expected = 'ลˆ'; $this->assertEquals($expected, $result); $string = 'ลŠ'; $result = Multibyte::strtolower($string); $expected = 'ล‹'; $this->assertEquals($expected, $result); $string = 'ลŒ'; $result = Multibyte::strtolower($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ลŽ'; $result = Multibyte::strtolower($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ล'; $result = Multibyte::strtolower($string); $expected = 'ล‘'; $this->assertEquals($expected, $result); $string = 'ล’'; $result = Multibyte::strtolower($string); $expected = 'ล“'; $this->assertEquals($expected, $result); $string = 'ล”'; $result = Multibyte::strtolower($string); $expected = 'ล•'; $this->assertEquals($expected, $result); $string = 'ล–'; $result = Multibyte::strtolower($string); $expected = 'ล—'; $this->assertEquals($expected, $result); $string = 'ล˜'; $result = Multibyte::strtolower($string); $expected = 'ล™'; $this->assertEquals($expected, $result); $string = 'ลš'; $result = Multibyte::strtolower($string); $expected = 'ล›'; $this->assertEquals($expected, $result); $string = 'ลœ'; $result = Multibyte::strtolower($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ลž'; $result = Multibyte::strtolower($string); $expected = 'ลŸ'; $this->assertEquals($expected, $result); $string = 'ล '; $result = Multibyte::strtolower($string); $expected = 'ลก'; $this->assertEquals($expected, $result); $string = 'ลข'; $result = Multibyte::strtolower($string); $expected = 'ลฃ'; $this->assertEquals($expected, $result); $string = 'ลค'; $result = Multibyte::strtolower($string); $expected = 'ลฅ'; $this->assertEquals($expected, $result); $string = 'ลฆ'; $result = Multibyte::strtolower($string); $expected = 'ลง'; $this->assertEquals($expected, $result); $string = 'ลจ'; $result = Multibyte::strtolower($string); $expected = 'ลฉ'; $this->assertEquals($expected, $result); $string = 'ลช'; $result = Multibyte::strtolower($string); $expected = 'ลซ'; $this->assertEquals($expected, $result); $string = 'ลฌ'; $result = Multibyte::strtolower($string); $expected = 'ลญ'; $this->assertEquals($expected, $result); $string = 'ลฎ'; $result = Multibyte::strtolower($string); $expected = 'ลฏ'; $this->assertEquals($expected, $result); $string = 'ลฐ'; $result = Multibyte::strtolower($string); $expected = 'ลฑ'; $this->assertEquals($expected, $result); $string = 'ลฒ'; $result = Multibyte::strtolower($string); $expected = 'ลณ'; $this->assertEquals($expected, $result); $string = 'ลด'; $result = Multibyte::strtolower($string); $expected = 'ลต'; $this->assertEquals($expected, $result); $string = 'ลถ'; $result = Multibyte::strtolower($string); $expected = 'ลท'; $this->assertEquals($expected, $result); $string = 'ลน'; $result = Multibyte::strtolower($string); $expected = 'ลบ'; $this->assertEquals($expected, $result); $string = 'ลป'; $result = Multibyte::strtolower($string); $expected = 'ลผ'; $this->assertEquals($expected, $result); $string = 'ลฝ'; $result = Multibyte::strtolower($string); $expected = 'ลพ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $result = Multibyte::strtolower($string); $expected = 'ฤฤƒฤ…ฤ‡ฤ‰ฤ‹ฤฤฤ‘ฤ“ฤ•ฤ—ฤ™ฤ›ฤฤŸฤกฤฃฤฅฤงฤฉฤซฤญฤฏฤณฤตฤทฤบฤผฤพล€ล‚ล„ล†ลˆล‹ลลล‘ล“ล•ล—ล™ล›ลลŸลกลฃลฅลงลฉลซลญลฏลฑลณลตลทลบลผลพ'; $this->assertEquals($expected, $result); $string = 'ฤคฤ’ฤนฤปลŽ, ลดลล˜ฤปฤŽ!'; $result = Multibyte::strtolower($string); $expected = 'ฤฅฤ“ฤบฤผล, ลตล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'ฤฅฤ“ฤบฤผล, ลตล‘ล™ฤผฤ!'; $result = Multibyte::strtolower($string); $expected = 'ฤฅฤ“ฤบฤผล, ลตล‘ล™ฤผฤ!'; $this->assertEquals($expected, $result); $string = 'แผˆฮ™'; $result = Multibyte::strtolower($string); $expected = 'แผ€ฮน'; $this->assertEquals($expected, $result); $string = 'ิ€ิ‚ิ„ิ†ิˆิŠิŒิŽิิ’'; $result = Multibyte::strtolower($string); $expected = 'ิิƒิ…ิ‡ิ‰ิ‹ิิิ‘ิ“'; $this->assertEquals($expected, $result); $string = 'ิฑิฒิณิดิติถิทิธินิบิปิผิฝิพิฟี€ีี‚ีƒี„ี…ี†ี‡ีˆี‰ีŠี‹ีŒีีŽีีี‘ี’ี“ี”ี•ี–ึ‡'; $result = Multibyte::strtolower($string); $expected = 'ีกีขีฃีคีฅีฆีงีจีฉีชีซีฌีญีฎีฏีฐีฑีฒีณีดีตีถีทีธีนีบีปีผีฝีพีฟึ€ึึ‚ึƒึ„ึ…ึ†ึ‡'; $this->assertEquals($expected, $result); $string = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $result = Multibyte::strtolower($string); $expected = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $this->assertEquals($expected, $result); $string = 'แธ€แธ‚แธ„แธ†แธˆแธŠแธŒแธŽแธแธ’แธ”แธ–แธ˜แธšแธœแธžแธ แธขแธคแธฆแธจแธชแธฌแธฎแธฐแธฒแธดแธถแธธแธบแธผแธพแน€แน‚แน„แน†แนˆแนŠแนŒแนŽแนแน’แน”แน–แน˜แนšแนœแนžแน แนขแนคแนฆแนจแนชแนฌแนฎแนฐแนฒแนดแนถแนธแนบแนผแนพแบ€แบ‚แบ„แบ†แบˆแบŠแบŒแบŽแบแบ’แบ”แบ–แบ—แบ˜แบ™แบšแบ แบขแบคแบฆแบจแบชแบฌแบฎแบฐแบฒแบดแบถแบธแบบแบผแบพแป€แป‚แป„แป†แปˆแปŠแปŒแปŽแปแป’แป”แป–แป˜แปšแปœแปžแป แปขแปคแปฆแปจแปชแปฌแปฎแปฐแปฒแปดแปถแปธ'; $result = Multibyte::strtolower($string); $expected = 'แธแธƒแธ…แธ‡แธ‰แธ‹แธแธแธ‘แธ“แธ•แธ—แธ™แธ›แธแธŸแธกแธฃแธฅแธงแธฉแธซแธญแธฏแธฑแธณแธตแธทแธนแธปแธฝแธฟแนแนƒแน…แน‡แน‰แน‹แนแนแน‘แน“แน•แน—แน™แน›แนแนŸแนกแนฃแนฅแนงแนฉแนซแนญแนฏแนฑแนณแนตแนทแนนแนปแนฝแนฟแบแบƒแบ…แบ‡แบ‰แบ‹แบแบแบ‘แบ“แบ•แบ–แบ—แบ˜แบ™แบšแบกแบฃแบฅแบงแบฉแบซแบญแบฏแบฑแบณแบตแบทแบนแบปแบฝแบฟแปแปƒแป…แป‡แป‰แป‹แปแปแป‘แป“แป•แป—แป™แป›แปแปŸแปกแปฃแปฅแปงแปฉแปซแปญแปฏแปฑแปณแปตแปทแปน'; $this->assertEquals($expected, $result); $string = 'โ„ฆโ„ชโ„ซโ„ฒ'; $result = Multibyte::strtolower($string); $expected = 'ฯ‰kรฅโ…Ž'; $this->assertEquals($expected, $result); $string = 'โ„ฆโ„ชโ„ซ'; $result = Multibyte::strtolower($string); $expected = 'ฯ‰kรฅ'; $this->assertEquals($expected, $result); $string = 'ฮฉKร…'; $result = Multibyte::strtolower($string); $expected = 'ฯ‰kรฅ'; $this->assertEquals($expected, $result); $string = 'โ… โ…กโ…ขโ…ฃโ…คโ…ฅโ…ฆโ…งโ…จโ…ฉโ…ชโ…ซโ…ฌโ…ญโ…ฎโ…ฏโ†ƒ'; $result = Multibyte::strtolower($string); $expected = 'โ…ฐโ…ฑโ…ฒโ…ณโ…ดโ…ตโ…ถโ…ทโ…ธโ…นโ…บโ…ปโ…ผโ…ฝโ…พโ…ฟโ†„'; $this->assertEquals($expected, $result); $string = 'โ’ถโ’ทโ’ธโ’นโ’บโ’ปโ’ผโ’ฝโ’พโ’ฟโ“€โ“โ“‚โ“ƒโ“„โ“…โ“†โ“‡โ“ˆโ“‰โ“Šโ“‹โ“Œโ“โ“Žโ“'; $result = Multibyte::strtolower($string); $expected = 'โ“โ“‘โ“’โ““โ“”โ“•โ“–โ“—โ“˜โ“™โ“šโ“›โ“œโ“โ“žโ“Ÿโ“ โ“กโ“ขโ“ฃโ“คโ“ฅโ“ฆโ“งโ“จโ“ฉ'; $this->assertEquals($expected, $result); $string = 'โฐ€โฐโฐ‚โฐƒโฐ„โฐ…โฐ†โฐ‡โฐˆโฐ‰โฐŠโฐ‹โฐŒโฐโฐŽโฐโฐโฐ‘โฐ’โฐ“โฐ”โฐ•โฐ–โฐ—โฐ˜โฐ™โฐšโฐ›โฐœโฐโฐžโฐŸโฐ โฐกโฐขโฐฃโฐคโฐฅโฐฆโฐงโฐจโฐฉโฐชโฐซโฐฌโฐญโฐฎ'; $result = Multibyte::strtolower($string); $expected = 'โฐฐโฐฑโฐฒโฐณโฐดโฐตโฐถโฐทโฐธโฐนโฐบโฐปโฐผโฐฝโฐพโฐฟโฑ€โฑโฑ‚โฑƒโฑ„โฑ…โฑ†โฑ‡โฑˆโฑ‰โฑŠโฑ‹โฑŒโฑโฑŽโฑโฑโฑ‘โฑ’โฑ“โฑ”โฑ•โฑ–โฑ—โฑ˜โฑ™โฑšโฑ›โฑœโฑโฑž'; $this->assertEquals($expected, $result); $string = 'โฒ€โฒ‚โฒ„โฒ†โฒˆโฒŠโฒŒโฒŽโฒโฒ’โฒ”โฒ–โฒ˜โฒšโฒœโฒžโฒ โฒขโฒคโฒฆโฒจโฒชโฒฌโฒฎโฒฐโฒฒโฒดโฒถโฒธโฒบโฒผโฒพโณ€โณ‚โณ„โณ†โณˆโณŠโณŒโณŽโณโณ’โณ”โณ–โณ˜โณšโณœโณžโณ โณข'; $result = Multibyte::strtolower($string); $expected = 'โฒโฒƒโฒ…โฒ‡โฒ‰โฒ‹โฒโฒโฒ‘โฒ“โฒ•โฒ—โฒ™โฒ›โฒโฒŸโฒกโฒฃโฒฅโฒงโฒฉโฒซโฒญโฒฏโฒฑโฒณโฒตโฒทโฒนโฒปโฒฝโฒฟโณโณƒโณ…โณ‡โณ‰โณ‹โณโณโณ‘โณ“โณ•โณ—โณ™โณ›โณโณŸโณกโณฃ'; $this->assertEquals($expected, $result); $string = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $result = Multibyte::strtolower($string); $expected = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $this->assertEquals($expected, $result); } /** * testUsingMbStrtoupper method * * @return void */ public function testUsingMbStrtoupper() { $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = mb_strtoupper($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $result = mb_strtoupper($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $this->assertEquals($expected, $result); $string = 'ร '; $result = mb_strtoupper($string); $expected = 'ร€'; $this->assertEquals($expected, $result); $string = 'รก'; $result = mb_strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รข'; $result = mb_strtoupper($string); $expected = 'ร‚'; $this->assertEquals($expected, $result); $string = 'รฃ'; $result = mb_strtoupper($string); $expected = 'รƒ'; $this->assertEquals($expected, $result); $string = 'รค'; $result = mb_strtoupper($string); $expected = 'ร„'; $this->assertEquals($expected, $result); $string = 'รฅ'; $result = mb_strtoupper($string); $expected = 'ร…'; $this->assertEquals($expected, $result); $string = 'รฆ'; $result = mb_strtoupper($string); $expected = 'ร†'; $this->assertEquals($expected, $result); $string = 'รง'; $result = mb_strtoupper($string); $expected = 'ร‡'; $this->assertEquals($expected, $result); $string = 'รจ'; $result = mb_strtoupper($string); $expected = 'รˆ'; $this->assertEquals($expected, $result); $string = 'รฉ'; $result = mb_strtoupper($string); $expected = 'ร‰'; $this->assertEquals($expected, $result); $string = 'รช'; $result = mb_strtoupper($string); $expected = 'รŠ'; $this->assertEquals($expected, $result); $string = 'รซ'; $result = mb_strtoupper($string); $expected = 'ร‹'; $this->assertEquals($expected, $result); $string = 'รฌ'; $result = mb_strtoupper($string); $expected = 'รŒ'; $this->assertEquals($expected, $result); $string = 'รญ'; $result = mb_strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รฎ'; $result = mb_strtoupper($string); $expected = 'รŽ'; $this->assertEquals($expected, $result); $string = 'รฏ'; $result = mb_strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รฐ'; $result = mb_strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รฑ'; $result = mb_strtoupper($string); $expected = 'ร‘'; $this->assertEquals($expected, $result); $string = 'รฒ'; $result = mb_strtoupper($string); $expected = 'ร’'; $this->assertEquals($expected, $result); $string = 'รณ'; $result = mb_strtoupper($string); $expected = 'ร“'; $this->assertEquals($expected, $result); $string = 'รด'; $result = mb_strtoupper($string); $expected = 'ร”'; $this->assertEquals($expected, $result); $string = 'รต'; $result = mb_strtoupper($string); $expected = 'ร•'; $this->assertEquals($expected, $result); $string = 'รถ'; $result = mb_strtoupper($string); $expected = 'ร–'; $this->assertEquals($expected, $result); $string = 'รธ'; $result = mb_strtoupper($string); $expected = 'ร˜'; $this->assertEquals($expected, $result); $string = 'รน'; $result = mb_strtoupper($string); $expected = 'ร™'; $this->assertEquals($expected, $result); $string = 'รบ'; $result = mb_strtoupper($string); $expected = 'รš'; $this->assertEquals($expected, $result); $string = 'รป'; $result = mb_strtoupper($string); $expected = 'ร›'; $this->assertEquals($expected, $result); $string = 'รผ'; $result = mb_strtoupper($string); $expected = 'รœ'; $this->assertEquals($expected, $result); $string = 'รฝ'; $result = mb_strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รพ'; $result = mb_strtoupper($string); $expected = 'รž'; $this->assertEquals($expected, $result); $string = 'ร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรธรนรบรปรผรฝรพ'; $result = mb_strtoupper($string); $expected = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = mb_strtoupper($string); $expected = 'ฤ€'; $this->assertEquals($expected, $result); $string = 'ฤƒ'; $result = mb_strtoupper($string); $expected = 'ฤ‚'; $this->assertEquals($expected, $result); $string = 'ฤ…'; $result = mb_strtoupper($string); $expected = 'ฤ„'; $this->assertEquals($expected, $result); $string = 'ฤ‡'; $result = mb_strtoupper($string); $expected = 'ฤ†'; $this->assertEquals($expected, $result); $string = 'ฤ‰'; $result = mb_strtoupper($string); $expected = 'ฤˆ'; $this->assertEquals($expected, $result); $string = 'ฤ‹'; $result = mb_strtoupper($string); $expected = 'ฤŠ'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = mb_strtoupper($string); $expected = 'ฤŒ'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = mb_strtoupper($string); $expected = 'ฤŽ'; $this->assertEquals($expected, $result); $string = 'ฤ‘'; $result = mb_strtoupper($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤ“'; $result = mb_strtoupper($string); $expected = 'ฤ’'; $this->assertEquals($expected, $result); $string = 'ฤ•'; $result = mb_strtoupper($string); $expected = 'ฤ”'; $this->assertEquals($expected, $result); $string = 'ฤ—'; $result = mb_strtoupper($string); $expected = 'ฤ–'; $this->assertEquals($expected, $result); $string = 'ฤ™'; $result = mb_strtoupper($string); $expected = 'ฤ˜'; $this->assertEquals($expected, $result); $string = 'ฤ›'; $result = mb_strtoupper($string); $expected = 'ฤš'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = mb_strtoupper($string); $expected = 'ฤœ'; $this->assertEquals($expected, $result); $string = 'ฤŸ'; $result = mb_strtoupper($string); $expected = 'ฤž'; $this->assertEquals($expected, $result); $string = 'ฤก'; $result = mb_strtoupper($string); $expected = 'ฤ '; $this->assertEquals($expected, $result); $string = 'ฤฃ'; $result = mb_strtoupper($string); $expected = 'ฤข'; $this->assertEquals($expected, $result); $string = 'ฤฅ'; $result = mb_strtoupper($string); $expected = 'ฤค'; $this->assertEquals($expected, $result); $string = 'ฤง'; $result = mb_strtoupper($string); $expected = 'ฤฆ'; $this->assertEquals($expected, $result); $string = 'ฤฉ'; $result = mb_strtoupper($string); $expected = 'ฤจ'; $this->assertEquals($expected, $result); $string = 'ฤซ'; $result = mb_strtoupper($string); $expected = 'ฤช'; $this->assertEquals($expected, $result); $string = 'ฤญ'; $result = mb_strtoupper($string); $expected = 'ฤฌ'; $this->assertEquals($expected, $result); $string = 'ฤฏ'; $result = mb_strtoupper($string); $expected = 'ฤฎ'; $this->assertEquals($expected, $result); $string = 'ฤณ'; $result = mb_strtoupper($string); $expected = 'ฤฒ'; $this->assertEquals($expected, $result); $string = 'ฤต'; $result = mb_strtoupper($string); $expected = 'ฤด'; $this->assertEquals($expected, $result); $string = 'ฤท'; $result = mb_strtoupper($string); $expected = 'ฤถ'; $this->assertEquals($expected, $result); $string = 'ฤบ'; $result = mb_strtoupper($string); $expected = 'ฤน'; $this->assertEquals($expected, $result); $string = 'ฤผ'; $result = mb_strtoupper($string); $expected = 'ฤป'; $this->assertEquals($expected, $result); $string = 'ฤพ'; $result = mb_strtoupper($string); $expected = 'ฤฝ'; $this->assertEquals($expected, $result); $string = 'ล€'; $result = mb_strtoupper($string); $expected = 'ฤฟ'; $this->assertEquals($expected, $result); $string = 'ล‚'; $result = mb_strtoupper($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ล„'; $result = mb_strtoupper($string); $expected = 'ลƒ'; $this->assertEquals($expected, $result); $string = 'ล†'; $result = mb_strtoupper($string); $expected = 'ล…'; $this->assertEquals($expected, $result); $string = 'ลˆ'; $result = mb_strtoupper($string); $expected = 'ล‡'; $this->assertEquals($expected, $result); $string = 'ล‹'; $result = mb_strtoupper($string); $expected = 'ลŠ'; $this->assertEquals($expected, $result); $string = 'ล'; $result = mb_strtoupper($string); $expected = 'ลŒ'; $this->assertEquals($expected, $result); $string = 'ล'; $result = mb_strtoupper($string); $expected = 'ลŽ'; $this->assertEquals($expected, $result); $string = 'ล‘'; $result = mb_strtoupper($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ล“'; $result = mb_strtoupper($string); $expected = 'ล’'; $this->assertEquals($expected, $result); $string = 'ล•'; $result = mb_strtoupper($string); $expected = 'ล”'; $this->assertEquals($expected, $result); $string = 'ล—'; $result = mb_strtoupper($string); $expected = 'ล–'; $this->assertEquals($expected, $result); $string = 'ล™'; $result = mb_strtoupper($string); $expected = 'ล˜'; $this->assertEquals($expected, $result); $string = 'ล›'; $result = mb_strtoupper($string); $expected = 'ลš'; $this->assertEquals($expected, $result); $string = 'ล'; $result = mb_strtoupper($string); $expected = 'ลœ'; $this->assertEquals($expected, $result); $string = 'ลŸ'; $result = mb_strtoupper($string); $expected = 'ลž'; $this->assertEquals($expected, $result); $string = 'ลก'; $result = mb_strtoupper($string); $expected = 'ล '; $this->assertEquals($expected, $result); $string = 'ลฃ'; $result = mb_strtoupper($string); $expected = 'ลข'; $this->assertEquals($expected, $result); $string = 'ลฅ'; $result = mb_strtoupper($string); $expected = 'ลค'; $this->assertEquals($expected, $result); $string = 'ลง'; $result = mb_strtoupper($string); $expected = 'ลฆ'; $this->assertEquals($expected, $result); $string = 'ลฉ'; $result = mb_strtoupper($string); $expected = 'ลจ'; $this->assertEquals($expected, $result); $string = 'ลซ'; $result = mb_strtoupper($string); $expected = 'ลช'; $this->assertEquals($expected, $result); $string = 'ลญ'; $result = mb_strtoupper($string); $expected = 'ลฌ'; $this->assertEquals($expected, $result); $string = 'ลฏ'; $result = mb_strtoupper($string); $expected = 'ลฎ'; $this->assertEquals($expected, $result); $string = 'ลฑ'; $result = mb_strtoupper($string); $expected = 'ลฐ'; $this->assertEquals($expected, $result); $string = 'ลณ'; $result = mb_strtoupper($string); $expected = 'ลฒ'; $this->assertEquals($expected, $result); $string = 'ลต'; $result = mb_strtoupper($string); $expected = 'ลด'; $this->assertEquals($expected, $result); $string = 'ลท'; $result = mb_strtoupper($string); $expected = 'ลถ'; $this->assertEquals($expected, $result); $string = 'ลบ'; $result = mb_strtoupper($string); $expected = 'ลน'; $this->assertEquals($expected, $result); $string = 'ลผ'; $result = mb_strtoupper($string); $expected = 'ลป'; $this->assertEquals($expected, $result); $string = 'ลพ'; $result = mb_strtoupper($string); $expected = 'ลฝ'; $this->assertEquals($expected, $result); $string = 'ฤฤƒฤ…ฤ‡ฤ‰ฤ‹ฤฤฤ‘ฤ“ฤ•ฤ—ฤ™ฤ›ฤฤŸฤกฤฃฤฅฤงฤฉฤซฤญฤฏฤณฤตฤทฤบฤผฤพล€ล‚ล„ล†ลˆล‹ลลล‘ล“ล•ล—ล™ล›ลลŸลกลฃลฅลงลฉลซลญลฏลฑลณลตลทลบลผลพ'; $result = mb_strtoupper($string); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = mb_strtoupper($string); $expected = 'ฤคฤ’ฤนฤปลŽ, ลดลล˜ฤปฤŽ!'; $this->assertEquals($expected, $result); $string = 'แผ€ฮน'; $result = mb_strtoupper($string); $expected = 'แผˆฮ™'; $this->assertEquals($expected, $result); $string = 'ิิƒิ…ิ‡ิ‰ิ‹ิิิิ’'; $result = mb_strtoupper($string); $expected = 'ิ€ิ‚ิ„ิ†ิˆิŠิŒิŽิิ’'; $this->assertEquals($expected, $result); $string = 'ีกีขีฃีคีฅีฆีงีจีฉีชีซีฌีญีฎีฏีฐีฑีฒีณีดีตีถีทีธีนีบีปีผีฝีพีฟึ€ึึ‚ึƒึ„ึ…ึ†ึ‡'; $result = mb_strtoupper($string); $expected = 'ิฑิฒิณิดิติถิทิธินิบิปิผิฝิพิฟี€ีี‚ีƒี„ี…ี†ี‡ีˆี‰ีŠี‹ีŒีีŽีีี‘ี’ี“ี”ี•ี–ึ‡'; $this->assertEquals($expected, $result); $string = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $result = mb_strtoupper($string); $expected = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $this->assertEquals($expected, $result); $string = 'แธแธƒแธ…แธ‡แธ‰แธ‹แธแธแธ‘แธ“แธ•แธ—แธ™แธ›แธแธŸแธกแธฃแธฅแธงแธฉแธซแธญแธฏแธฑแธณแธตแธทแธนแธปแธฝแธฟแนแนƒแน…แน‡แน‰แน‹แนแนแน‘แน“แน•แน—แน™แน›แนแนŸแนกแนฃแนฅแนงแนฉแนซแนญแนฏแนฑแนณแนตแนทแนนแนปแนฝแนฟแบแบƒแบ…แบ‡แบ‰แบ‹แบแบแบ‘แบ“แบ•แบ–แบ—แบ˜แบ™แบšแบกแบฃแบฅแบงแบฉแบซแบญแบฏแบฑแบณแบตแบทแบนแบปแบฝแบฟแปแปƒแป…แป‡แป‰แป‹แปแปแป‘แป“แป•แป—แป™แป›แปแปŸแปกแปฃแปฅแปงแปฉแปซแปญแปฏแปฑแปณแปตแปทแปน'; $result = mb_strtoupper($string); $expected = 'แธ€แธ‚แธ„แธ†แธˆแธŠแธŒแธŽแธแธ’แธ”แธ–แธ˜แธšแธœแธžแธ แธขแธคแธฆแธจแธชแธฌแธฎแธฐแธฒแธดแธถแธธแธบแธผแธพแน€แน‚แน„แน†แนˆแนŠแนŒแนŽแนแน’แน”แน–แน˜แนšแนœแนžแน แนขแนคแนฆแนจแนชแนฌแนฎแนฐแนฒแนดแนถแนธแนบแนผแนพแบ€แบ‚แบ„แบ†แบˆแบŠแบŒแบŽแบแบ’แบ”แบ–แบ—แบ˜แบ™แบšแบ แบขแบคแบฆแบจแบชแบฌแบฎแบฐแบฒแบดแบถแบธแบบแบผแบพแป€แป‚แป„แป†แปˆแปŠแปŒแปŽแปแป’แป”แป–แป˜แปšแปœแปžแป แปขแปคแปฆแปจแปชแปฌแปฎแปฐแปฒแปดแปถแปธ'; $this->assertEquals($expected, $result); $string = 'ฯ‰kรฅ'; $result = mb_strtoupper($string); $expected = 'ฮฉKร…'; $this->assertEquals($expected, $result); $string = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $result = mb_strtoupper($string); $expected = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $this->assertEquals($expected, $result); } /** * testMultibyteStrtoupper method * * @return void */ public function testMultibyteStrtoupper() { $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = Multibyte::strtoupper($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $result = Multibyte::strtoupper($string); $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; $this->assertEquals($expected, $result); $string = 'ร '; $result = Multibyte::strtoupper($string); $expected = 'ร€'; $this->assertEquals($expected, $result); $string = 'รก'; $result = Multibyte::strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รข'; $result = Multibyte::strtoupper($string); $expected = 'ร‚'; $this->assertEquals($expected, $result); $string = 'รฃ'; $result = Multibyte::strtoupper($string); $expected = 'รƒ'; $this->assertEquals($expected, $result); $string = 'รค'; $result = Multibyte::strtoupper($string); $expected = 'ร„'; $this->assertEquals($expected, $result); $string = 'รฅ'; $result = Multibyte::strtoupper($string); $expected = 'ร…'; $this->assertEquals($expected, $result); $string = 'รฆ'; $result = Multibyte::strtoupper($string); $expected = 'ร†'; $this->assertEquals($expected, $result); $string = 'รง'; $result = Multibyte::strtoupper($string); $expected = 'ร‡'; $this->assertEquals($expected, $result); $string = 'รจ'; $result = Multibyte::strtoupper($string); $expected = 'รˆ'; $this->assertEquals($expected, $result); $string = 'รฉ'; $result = Multibyte::strtoupper($string); $expected = 'ร‰'; $this->assertEquals($expected, $result); $string = 'รช'; $result = Multibyte::strtoupper($string); $expected = 'รŠ'; $this->assertEquals($expected, $result); $string = 'รซ'; $result = Multibyte::strtoupper($string); $expected = 'ร‹'; $this->assertEquals($expected, $result); $string = 'รฌ'; $result = Multibyte::strtoupper($string); $expected = 'รŒ'; $this->assertEquals($expected, $result); $string = 'รญ'; $result = Multibyte::strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รฎ'; $result = Multibyte::strtoupper($string); $expected = 'รŽ'; $this->assertEquals($expected, $result); $string = 'รฏ'; $result = Multibyte::strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รฐ'; $result = Multibyte::strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รฑ'; $result = Multibyte::strtoupper($string); $expected = 'ร‘'; $this->assertEquals($expected, $result); $string = 'รฒ'; $result = Multibyte::strtoupper($string); $expected = 'ร’'; $this->assertEquals($expected, $result); $string = 'รณ'; $result = Multibyte::strtoupper($string); $expected = 'ร“'; $this->assertEquals($expected, $result); $string = 'รด'; $result = Multibyte::strtoupper($string); $expected = 'ร”'; $this->assertEquals($expected, $result); $string = 'รต'; $result = Multibyte::strtoupper($string); $expected = 'ร•'; $this->assertEquals($expected, $result); $string = 'รถ'; $result = Multibyte::strtoupper($string); $expected = 'ร–'; $this->assertEquals($expected, $result); $string = 'รธ'; $result = Multibyte::strtoupper($string); $expected = 'ร˜'; $this->assertEquals($expected, $result); $string = 'รน'; $result = Multibyte::strtoupper($string); $expected = 'ร™'; $this->assertEquals($expected, $result); $string = 'รบ'; $result = Multibyte::strtoupper($string); $expected = 'รš'; $this->assertEquals($expected, $result); $string = 'รป'; $result = Multibyte::strtoupper($string); $expected = 'ร›'; $this->assertEquals($expected, $result); $string = 'รผ'; $result = Multibyte::strtoupper($string); $expected = 'รœ'; $this->assertEquals($expected, $result); $string = 'รฝ'; $result = Multibyte::strtoupper($string); $expected = 'ร'; $this->assertEquals($expected, $result); $string = 'รพ'; $result = Multibyte::strtoupper($string); $expected = 'รž'; $this->assertEquals($expected, $result); $string = 'ร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรธรนรบรปรผรฝรพ'; $result = Multibyte::strtoupper($string); $expected = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = Multibyte::strtoupper($string); $expected = 'ฤ€'; $this->assertEquals($expected, $result); $string = 'ฤƒ'; $result = Multibyte::strtoupper($string); $expected = 'ฤ‚'; $this->assertEquals($expected, $result); $string = 'ฤ…'; $result = Multibyte::strtoupper($string); $expected = 'ฤ„'; $this->assertEquals($expected, $result); $string = 'ฤ‡'; $result = Multibyte::strtoupper($string); $expected = 'ฤ†'; $this->assertEquals($expected, $result); $string = 'ฤ‰'; $result = Multibyte::strtoupper($string); $expected = 'ฤˆ'; $this->assertEquals($expected, $result); $string = 'ฤ‹'; $result = Multibyte::strtoupper($string); $expected = 'ฤŠ'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = Multibyte::strtoupper($string); $expected = 'ฤŒ'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = Multibyte::strtoupper($string); $expected = 'ฤŽ'; $this->assertEquals($expected, $result); $string = 'ฤ‘'; $result = Multibyte::strtoupper($string); $expected = 'ฤ'; $this->assertEquals($expected, $result); $string = 'ฤ“'; $result = Multibyte::strtoupper($string); $expected = 'ฤ’'; $this->assertEquals($expected, $result); $string = 'ฤ•'; $result = Multibyte::strtoupper($string); $expected = 'ฤ”'; $this->assertEquals($expected, $result); $string = 'ฤ—'; $result = Multibyte::strtoupper($string); $expected = 'ฤ–'; $this->assertEquals($expected, $result); $string = 'ฤ™'; $result = Multibyte::strtoupper($string); $expected = 'ฤ˜'; $this->assertEquals($expected, $result); $string = 'ฤ›'; $result = Multibyte::strtoupper($string); $expected = 'ฤš'; $this->assertEquals($expected, $result); $string = 'ฤ'; $result = Multibyte::strtoupper($string); $expected = 'ฤœ'; $this->assertEquals($expected, $result); $string = 'ฤŸ'; $result = Multibyte::strtoupper($string); $expected = 'ฤž'; $this->assertEquals($expected, $result); $string = 'ฤก'; $result = Multibyte::strtoupper($string); $expected = 'ฤ '; $this->assertEquals($expected, $result); $string = 'ฤฃ'; $result = Multibyte::strtoupper($string); $expected = 'ฤข'; $this->assertEquals($expected, $result); $string = 'ฤฅ'; $result = Multibyte::strtoupper($string); $expected = 'ฤค'; $this->assertEquals($expected, $result); $string = 'ฤง'; $result = Multibyte::strtoupper($string); $expected = 'ฤฆ'; $this->assertEquals($expected, $result); $string = 'ฤฉ'; $result = Multibyte::strtoupper($string); $expected = 'ฤจ'; $this->assertEquals($expected, $result); $string = 'ฤซ'; $result = Multibyte::strtoupper($string); $expected = 'ฤช'; $this->assertEquals($expected, $result); $string = 'ฤญ'; $result = Multibyte::strtoupper($string); $expected = 'ฤฌ'; $this->assertEquals($expected, $result); $string = 'ฤฏ'; $result = Multibyte::strtoupper($string); $expected = 'ฤฎ'; $this->assertEquals($expected, $result); $string = 'ฤณ'; $result = Multibyte::strtoupper($string); $expected = 'ฤฒ'; $this->assertEquals($expected, $result); $string = 'ฤต'; $result = Multibyte::strtoupper($string); $expected = 'ฤด'; $this->assertEquals($expected, $result); $string = 'ฤท'; $result = Multibyte::strtoupper($string); $expected = 'ฤถ'; $this->assertEquals($expected, $result); $string = 'ฤบ'; $result = Multibyte::strtoupper($string); $expected = 'ฤน'; $this->assertEquals($expected, $result); $string = 'ฤผ'; $result = Multibyte::strtoupper($string); $expected = 'ฤป'; $this->assertEquals($expected, $result); $string = 'ฤพ'; $result = Multibyte::strtoupper($string); $expected = 'ฤฝ'; $this->assertEquals($expected, $result); $string = 'ล€'; $result = Multibyte::strtoupper($string); $expected = 'ฤฟ'; $this->assertEquals($expected, $result); $string = 'ล‚'; $result = Multibyte::strtoupper($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ล„'; $result = Multibyte::strtoupper($string); $expected = 'ลƒ'; $this->assertEquals($expected, $result); $string = 'ล†'; $result = Multibyte::strtoupper($string); $expected = 'ล…'; $this->assertEquals($expected, $result); $string = 'ลˆ'; $result = Multibyte::strtoupper($string); $expected = 'ล‡'; $this->assertEquals($expected, $result); $string = 'ล‹'; $result = Multibyte::strtoupper($string); $expected = 'ลŠ'; $this->assertEquals($expected, $result); $string = 'ล'; $result = Multibyte::strtoupper($string); $expected = 'ลŒ'; $this->assertEquals($expected, $result); $string = 'ล'; $result = Multibyte::strtoupper($string); $expected = 'ลŽ'; $this->assertEquals($expected, $result); $string = 'ล‘'; $result = Multibyte::strtoupper($string); $expected = 'ล'; $this->assertEquals($expected, $result); $string = 'ล“'; $result = Multibyte::strtoupper($string); $expected = 'ล’'; $this->assertEquals($expected, $result); $string = 'ล•'; $result = Multibyte::strtoupper($string); $expected = 'ล”'; $this->assertEquals($expected, $result); $string = 'ล—'; $result = Multibyte::strtoupper($string); $expected = 'ล–'; $this->assertEquals($expected, $result); $string = 'ล™'; $result = Multibyte::strtoupper($string); $expected = 'ล˜'; $this->assertEquals($expected, $result); $string = 'ล›'; $result = Multibyte::strtoupper($string); $expected = 'ลš'; $this->assertEquals($expected, $result); $string = 'ล'; $result = Multibyte::strtoupper($string); $expected = 'ลœ'; $this->assertEquals($expected, $result); $string = 'ลŸ'; $result = Multibyte::strtoupper($string); $expected = 'ลž'; $this->assertEquals($expected, $result); $string = 'ลก'; $result = Multibyte::strtoupper($string); $expected = 'ล '; $this->assertEquals($expected, $result); $string = 'ลฃ'; $result = Multibyte::strtoupper($string); $expected = 'ลข'; $this->assertEquals($expected, $result); $string = 'ลฅ'; $result = Multibyte::strtoupper($string); $expected = 'ลค'; $this->assertEquals($expected, $result); $string = 'ลง'; $result = Multibyte::strtoupper($string); $expected = 'ลฆ'; $this->assertEquals($expected, $result); $string = 'ลฉ'; $result = Multibyte::strtoupper($string); $expected = 'ลจ'; $this->assertEquals($expected, $result); $string = 'ลซ'; $result = Multibyte::strtoupper($string); $expected = 'ลช'; $this->assertEquals($expected, $result); $string = 'ลญ'; $result = Multibyte::strtoupper($string); $expected = 'ลฌ'; $this->assertEquals($expected, $result); $string = 'ลฏ'; $result = Multibyte::strtoupper($string); $expected = 'ลฎ'; $this->assertEquals($expected, $result); $string = 'ลฑ'; $result = Multibyte::strtoupper($string); $expected = 'ลฐ'; $this->assertEquals($expected, $result); $string = 'ลณ'; $result = Multibyte::strtoupper($string); $expected = 'ลฒ'; $this->assertEquals($expected, $result); $string = 'ลต'; $result = Multibyte::strtoupper($string); $expected = 'ลด'; $this->assertEquals($expected, $result); $string = 'ลท'; $result = Multibyte::strtoupper($string); $expected = 'ลถ'; $this->assertEquals($expected, $result); $string = 'ลบ'; $result = Multibyte::strtoupper($string); $expected = 'ลน'; $this->assertEquals($expected, $result); $string = 'ลผ'; $result = Multibyte::strtoupper($string); $expected = 'ลป'; $this->assertEquals($expected, $result); $string = 'ลพ'; $result = Multibyte::strtoupper($string); $expected = 'ลฝ'; $this->assertEquals($expected, $result); $string = 'ฤฤƒฤ…ฤ‡ฤ‰ฤ‹ฤฤฤ‘ฤ“ฤ•ฤ—ฤ™ฤ›ฤฤŸฤกฤฃฤฅฤงฤฉฤซฤญฤฏฤณฤตฤทฤบฤผฤพล€ล‚ล„ล†ลˆล‹ลลล‘ล“ล•ล—ล™ล›ลลŸลกลฃลฅลงลฉลซลญลฏลฑลณลตลทลบลผลพ'; $result = Multibyte::strtoupper($string); $expected = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = Multibyte::strtoupper($string); $expected = 'ฤคฤ’ฤนฤปลŽ, ลดลล˜ฤปฤŽ!'; $this->assertEquals($expected, $result); $string = 'แผ€ฮน'; $result = mb_strtoupper($string); $expected = 'แผˆฮ™'; $this->assertEquals($expected, $result); $string = 'แผ€ฮน'; $result = Multibyte::strtoupper($string); $expected = 'แผˆฮ™'; $this->assertEquals($expected, $result); $string = 'ิิƒิ…ิ‡ิ‰ิ‹ิิิิ’'; $result = Multibyte::strtoupper($string); $expected = 'ิ€ิ‚ิ„ิ†ิˆิŠิŒิŽิิ’'; $this->assertEquals($expected, $result); $string = 'ีกีขีฃีคีฅีฆีงีจีฉีชีซีฌีญีฎีฏีฐีฑีฒีณีดีตีถีทีธีนีบีปีผีฝีพีฟึ€ึึ‚ึƒึ„ึ…ึ†ึ‡'; $result = Multibyte::strtoupper($string); $expected = 'ิฑิฒิณิดิติถิทิธินิบิปิผิฝิพิฟี€ีี‚ีƒี„ี…ี†ี‡ีˆี‰ีŠี‹ีŒีีŽีีี‘ี’ี“ี”ี•ี–ึ‡'; $this->assertEquals($expected, $result); $string = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $result = Multibyte::strtoupper($string); $expected = 'แ‚ แ‚กแ‚ขแ‚ฃแ‚คแ‚ฅแ‚ฆแ‚งแ‚จแ‚ฉแ‚ชแ‚ซแ‚ฌแ‚ญแ‚ฎแ‚ฏแ‚ฐแ‚ฑแ‚ฒแ‚ณแ‚ดแ‚ตแ‚ถแ‚ทแ‚ธแ‚นแ‚บแ‚ปแ‚ผแ‚ฝแ‚พแ‚ฟแƒ€แƒแƒ‚แƒƒแƒ„แƒ…'; $this->assertEquals($expected, $result); $string = 'แธแธƒแธ…แธ‡แธ‰แธ‹แธแธแธ‘แธ“แธ•แธ—แธ™แธ›แธแธŸแธกแธฃแธฅแธงแธฉแธซแธญแธฏแธฑแธณแธตแธทแธนแธปแธฝแธฟแนแนƒแน…แน‡แน‰แน‹แนแนแน‘แน“แน•แน—แน™แน›แนแนŸแนกแนฃแนฅแนงแนฉแนซแนญแนฏแนฑแนณแนตแนทแนนแนปแนฝแนฟแบแบƒแบ…แบ‡แบ‰แบ‹แบแบแบ‘แบ“แบ•แบ–แบ—แบ˜แบ™แบšแบกแบฃแบฅแบงแบฉแบซแบญแบฏแบฑแบณแบตแบทแบนแบปแบฝแบฟแปแปƒแป…แป‡แป‰แป‹แปแปแป‘แป“แป•แป—แป™แป›แปแปŸแปกแปฃแปฅแปงแปฉแปซแปญแปฏแปฑแปณแปตแปทแปน'; $result = Multibyte::strtoupper($string); $expected = 'แธ€แธ‚แธ„แธ†แธˆแธŠแธŒแธŽแธแธ’แธ”แธ–แธ˜แธšแธœแธžแธ แธขแธคแธฆแธจแธชแธฌแธฎแธฐแธฒแธดแธถแธธแธบแธผแธพแน€แน‚แน„แน†แนˆแนŠแนŒแนŽแนแน’แน”แน–แน˜แนšแนœแนžแน แนขแนคแนฆแนจแนชแนฌแนฎแนฐแนฒแนดแนถแนธแนบแนผแนพแบ€แบ‚แบ„แบ†แบˆแบŠแบŒแบŽแบแบ’แบ”แบ–แบ—แบ˜แบ™แบšแบ แบขแบคแบฆแบจแบชแบฌแบฎแบฐแบฒแบดแบถแบธแบบแบผแบพแป€แป‚แป„แป†แปˆแปŠแปŒแปŽแปแป’แป”แป–แป˜แปšแปœแปžแป แปขแปคแปฆแปจแปชแปฌแปฎแปฐแปฒแปดแปถแปธ'; $this->assertEquals($expected, $result); $string = 'ฯ‰kรฅโ…Ž'; $result = Multibyte::strtoupper($string); $expected = 'ฮฉKร…โ„ฒ'; $this->assertEquals($expected, $result); $string = 'ฯ‰kรฅ'; $result = Multibyte::strtoupper($string); $expected = 'ฮฉKร…'; $this->assertEquals($expected, $result); $string = 'โ…ฐโ…ฑโ…ฒโ…ณโ…ดโ…ตโ…ถโ…ทโ…ธโ…นโ…บโ…ปโ…ผโ…ฝโ…พโ…ฟโ†„'; $result = Multibyte::strtoupper($string); $expected = 'โ… โ…กโ…ขโ…ฃโ…คโ…ฅโ…ฆโ…งโ…จโ…ฉโ…ชโ…ซโ…ฌโ…ญโ…ฎโ…ฏโ†ƒ'; $this->assertEquals($expected, $result); $string = 'โ“โ“‘โ“’โ““โ“”โ“•โ“–โ“—โ“˜โ“™โ“šโ“›โ“œโ“โ“žโ“Ÿโ“ โ“กโ“ขโ“ฃโ“คโ“ฅโ“ฆโ“งโ“จโ“ฉ'; $result = Multibyte::strtoupper($string); $expected = 'โ’ถโ’ทโ’ธโ’นโ’บโ’ปโ’ผโ’ฝโ’พโ’ฟโ“€โ“โ“‚โ“ƒโ“„โ“…โ“†โ“‡โ“ˆโ“‰โ“Šโ“‹โ“Œโ“โ“Žโ“'; $this->assertEquals($expected, $result); $string = 'โฐฐโฐฑโฐฒโฐณโฐดโฐตโฐถโฐทโฐธโฐนโฐบโฐปโฐผโฐฝโฐพโฐฟโฑ€โฑโฑ‚โฑƒโฑ„โฑ…โฑ†โฑ‡โฑˆโฑ‰โฑŠโฑ‹โฑŒโฑโฑŽโฑโฑโฑ‘โฑ’โฑ“โฑ”โฑ•โฑ–โฑ—โฑ˜โฑ™โฑšโฑ›โฑœโฑโฑž'; $result = Multibyte::strtoupper($string); $expected = 'โฐ€โฐโฐ‚โฐƒโฐ„โฐ…โฐ†โฐ‡โฐˆโฐ‰โฐŠโฐ‹โฐŒโฐโฐŽโฐโฐโฐ‘โฐ’โฐ“โฐ”โฐ•โฐ–โฐ—โฐ˜โฐ™โฐšโฐ›โฐœโฐโฐžโฐŸโฐ โฐกโฐขโฐฃโฐคโฐฅโฐฆโฐงโฐจโฐฉโฐชโฐซโฐฌโฐญโฐฎ'; $this->assertEquals($expected, $result); $string = 'โฒโฒƒโฒ…โฒ‡โฒ‰โฒ‹โฒโฒโฒ‘โฒ“โฒ•โฒ—โฒ™โฒ›โฒโฒŸโฒกโฒฃโฒฅโฒงโฒฉโฒซโฒญโฒฏโฒฑโฒณโฒตโฒทโฒนโฒปโฒฝโฒฟโณโณƒโณ…โณ‡โณ‰โณ‹โณโณโณ‘โณ“โณ•โณ—โณ™โณ›โณโณŸโณกโณฃ'; $result = Multibyte::strtoupper($string); $expected = 'โฒ€โฒ‚โฒ„โฒ†โฒˆโฒŠโฒŒโฒŽโฒโฒ’โฒ”โฒ–โฒ˜โฒšโฒœโฒžโฒ โฒขโฒคโฒฆโฒจโฒชโฒฌโฒฎโฒฐโฒฒโฒดโฒถโฒธโฒบโฒผโฒพโณ€โณ‚โณ„โณ†โณˆโณŠโณŒโณŽโณโณ’โณ”โณ–โณ˜โณšโณœโณžโณ โณข'; $this->assertEquals($expected, $result); $string = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $result = Multibyte::strtoupper($string); $expected = '๏ฌ€๏ฌ๏ฌ‚๏ฌƒ๏ฌ„๏ฌ…๏ฌ†๏ฌ“๏ฌ”๏ฌ•๏ฌ–๏ฌ—'; $this->assertEquals($expected, $result); } /** * testUsingMbSubstrCount method * * @return void */ public function testUsingMbSubstrCount() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSFTUVWXYZ0F12345F6789'; $find = 'F'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰ร…รŠร‹รŒรรŽรรร‘ร’ร“ร”ร…ร•ร–ร˜ร…ร™รšร›ร…รœรรž'; $find = 'ร…'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร™รšร‚รƒร„ร…ร†ร‡รˆร™รšร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรžร™รš'; $find = 'ร™รš'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร…ร‹รŒรรŽรรร‘ร’ร“ร”ร•ร…ร–ร˜ร…ร™รšร…ร›รœร…รรžร…'; $find = 'ร…'; $result = mb_substr_count($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ฤŠฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤŠฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤŠฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒฤŠลŽลล’ล”ล–ล˜ลšลœลžล ลขฤŠลคลฆลจลชลฌลฎลฐฤŠลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_substr_count($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤŠฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ฤŠล‡ลŠลŒลŽลล’ล”ล–ฤŠล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./012F34567F89:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiFjklmnopqFrstuvwFxyz{|}~'; $find = 'F'; $result = mb_substr_count($string, $find); $expected = 6; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยตยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รยตร‚รƒยตร„ร…ร†ร‡ยตรˆ'; $find = 'ยต'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดร•ร–รตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ร•ร–ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤร•ร–ฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆร•ร–ฤงฤจฤฉฤชฤซฤฌ'; $find = 'ร•ร–'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลฤตฤถฤทฤธฤนลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกฤตฤถฤทฤธฤนลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณฤตฤถฤทฤธฤนลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ฤตฤถฤทฤธฤน'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦธฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠฦธว‹วŒววŽววว‘ว’ว“ฦธว”ว•ว–ว—ว˜ว™วšฦธว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦนฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦนฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦนฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚ฦนวƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžส€ษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส€ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงส€สจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบส€สปสผ'; $find = 'ส€'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะ‡ะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = mb_substr_count($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะ ะขะฃะคะฅะฆะงะจะฉะชะซะฌะ ะญะฎะฏะฐะฑะ ะฒะณะดะตะถะทะธะนะบะปะ ะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกั€ะขะฃะคะฅะฆะงะจะฉะชะซั€ะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ูู†ู‚ูƒู„ู†ู…ู†ู‡ูˆู†ู‰ูŠู†ู‹ูŒููŽู'; $find = 'ู†'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœฟโœดโœตโœถโœทโœธโœฟโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โœฟโ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบโบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โบโปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝคโฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝคโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = mb_substr_count($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆบ๋ˆป๋ˆผ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋ˆบ๋ˆป๋ˆผ๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋ˆบ๋ˆป๋ˆผ๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆบ๋ˆป๋ˆผ'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = '๏บž๏บŸ๏บ ๏บก๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บž๏บŸ๏บ ๏บก๏บ†๏บ‡๏บž๏บŸ๏บ ๏บก๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž๏บŸ๏บ ๏บก'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏ปž๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ปž๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปž๏ปธ๏ปน๏ปบ๏ปž๏ปป๏ปผ'; $find = '๏ปž'; $result = mb_substr_count($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ‹๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ‹๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = mb_substr_count($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝ‹๏ฝŒ๏ฝ๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ‹๏ฝŒ๏ฝ๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹๏ฝŒ๏ฝ'; $result = mb_substr_count($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ๏ฝ๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ๏ฝ๏ฝ…'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ฤบฤผ'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = mb_substr_count($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rl'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'niฤiniฤiini'; $find = 'n'; $result = mb_substr_count($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'moฤ‡imoฤ‡imoฤ‡mฤ‡ioฤ‡i'; $find = 'ฤ‡i'; $result = mb_substr_count($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = mb_substr_count($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'H'; $result = mb_substr_count($string, $find); $expected = 0; $this->assertEquals($expected, $result); } /** * testMultibyteSubstrCount method * * @return void */ public function testMultibyteSubstrCount() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $find = 'F'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ABCDEFGHIJKLMNOPQFRSFTUVWXYZ0F12345F6789'; $find = 'F'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰ร…รŠร‹รŒรรŽรรร‘ร’ร“ร”ร…ร•ร–ร˜ร…ร™รšร›ร…รœรรž'; $find = 'ร…'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร€รร™รšร‚รƒร„ร…ร†ร‡รˆร™รšร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร…ร™รšร›รœรรžร™รš'; $find = 'ร™รš'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร…ร‹รŒรรŽรรร‘ร’ร“ร”ร•ร…ร–ร˜ร…ร™รšร…ร›รœร…รรžร…'; $find = 'ร…'; $result = Multibyte::substrCount($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ฤŠฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤŠฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤŠฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒฤŠลŽลล’ล”ล–ล˜ลšลœลžล ลขฤŠลคลฆลจลชลฌลฎลฐฤŠลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::substrCount($string, $find); $expected = 7; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤŠฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลฤŠลƒล…ฤŠล‡ลŠลŒลŽลล’ล”ล–ฤŠล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $find = 'ฤŠ'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./012F34567F89:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiFjklmnopqFrstuvwFxyz{|}~'; $find = 'F'; $result = Multibyte::substrCount($string, $find); $expected = 6; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยตยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รยตร‚รƒยตร„ร…ร†ร‡ยตรˆ'; $find = 'ยต'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดร•ร–รตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ร•ร–ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤร•ร–ฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆร•ร–ฤงฤจฤฉฤชฤซฤฌ'; $find = 'ร•ร–'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลฤตฤถฤทฤธฤนลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกฤตฤถฤทฤธฤนลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณฤตฤถฤทฤธฤนลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $find = 'ฤตฤถฤทฤธฤน'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦธฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠฦธว‹วŒววŽววว‘ว’ว“ฦธว”ว•ว–ว—ว˜ว™วšฦธว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦธ'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦนฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦนฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦนฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚ฦนวƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $find = 'ฦน'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžส€ษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส€ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงส€สจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบส€สปสผ'; $find = 'ส€'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะ‡ะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $find = 'ะ‡'; $result = Multibyte::substrCount($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะ ะขะฃะคะฅะฆะงะจะฉะชะซะฌะ ะญะฎะฏะฐะฑะ ะฒะณะดะตะถะทะธะนะบะปะ ะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ะ '; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกั€ะขะฃะคะฅะฆะงะจะฉะชะซั€ะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $find = 'ั€'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'ูู†ู‚ูƒู„ู†ู…ู†ู‡ูˆู†ู‰ูŠู†ู‹ูŒููŽู'; $find = 'ู†'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœฟโœดโœตโœถโœทโœธโœฟโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โœฟโ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $find = 'โœฟ'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบโบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โบโปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $find = 'โบ'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝคโฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝคโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $find = 'โฝค'; $result = Multibyte::substrCount($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆบ๋ˆป๋ˆผ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋ˆบ๋ˆป๋ˆผ๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋ˆบ๋ˆป๋ˆผ๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $find = '๋ˆบ๋ˆป๋ˆผ'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = '๏บž๏บŸ๏บ ๏บก๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บž๏บŸ๏บ ๏บก๏บ†๏บ‡๏บž๏บŸ๏บ ๏บก๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $find = '๏บž๏บŸ๏บ ๏บก'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏ปž๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ปž๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปž๏ปธ๏ปน๏ปบ๏ปž๏ปป๏ปผ'; $find = '๏ปž'; $result = Multibyte::substrCount($string, $find); $expected = 5; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ‹๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ‹๏ฝ™๏ฝš'; $find = '๏ฝ‹'; $result = Multibyte::substrCount($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝ‹๏ฝŒ๏ฝ๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ‹๏ฝŒ๏ฝ๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ‹๏ฝŒ๏ฝ'; $result = Multibyte::substrCount($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ๏ฝ๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $find = '๏ฝ๏ฝ๏ฝ…'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $find = '๏ฝฑ'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $find = '๏พŠ'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ล‘'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'ฤบฤผ'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'o'; $result = Multibyte::substrCount($string, $find); $expected = 2; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $find = 'rl'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤini'; $find = 'n'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'niฤiniฤiini'; $find = 'n'; $result = Multibyte::substrCount($string, $find); $expected = 3; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $find = 'ฤ‡'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'moฤ‡imoฤ‡imoฤ‡mฤ‡ioฤ‡i'; $find = 'ฤ‡i'; $result = Multibyte::substrCount($string, $find); $expected = 4; $this->assertEquals($expected, $result); $string = 'drลพavni'; $find = 'ลพ'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $find = '่ฎพ'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $find = 'ๅ‘จ'; $result = Multibyte::substrCount($string, $find); $expected = 1; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $find = 'H'; $result = Multibyte::substrCount($string, $find); $expected = 0; $this->assertEquals($expected, $result); } /** * testUsingMbSubstr method * * @return void */ public function testUsingMbSubstr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $result = mb_substr($string, 4, 7); $expected = 'EFGHIJK'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $result = mb_substr($string, 4, 7); $expected = 'ร„ร…ร†ร‡รˆร‰รŠ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $result = mb_substr($string, 4, 7); $expected = 'ฤˆฤŠฤŒฤŽฤฤ’ฤ”'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = mb_substr($string, 4, 7); $expected = '%&\'()*+'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $result = mb_substr($string, 4); $expected = 'ยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $result = mb_substr($string, 4, 7); $expected = 'รรŽรรร‘ร’ร“'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $result = mb_substr($string, 4, 7); $expected = 'ฤฑฤฒฤณฤดฤตฤถฤท'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $result = mb_substr($string, 25); $expected = 'ฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $result = mb_substr($string, 3); $expected = 'ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $result = mb_substr($string, 3); $expected = 'ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $result = mb_substr($string, 3, 16); $expected = 'ะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $result = mb_substr($string, 3, 6); $expected = 'ู„ู…ู†ู‡ูˆู‰'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $result = mb_substr($string, 6, 14); $expected = 'โœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $result = mb_substr($string, 8, 13); $expected = 'โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $result = mb_substr($string, 12, 24); $expected = 'โฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $result = mb_substr($string, 12, 24); $expected = '๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $result = mb_substr($string, 12); $expected = '๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $result = mb_substr($string, 24, 12); $expected = '๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $result = mb_substr($string, 11, 2); $expected = '๏ฝŒ๏ฝ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $result = mb_substr($string, 7, 11); $expected = '๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $result = mb_substr($string, 13, 13); $expected = '๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = mb_substr($string, 3, 4); $expected = 'ฤผล, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $result = mb_substr($string, 3, 4); $expected = 'lo, '; $this->assertEquals($expected, $result); $string = 'ฤini'; $result = mb_substr($string, 3); $expected = 'i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $result = mb_substr($string, 1); $expected = 'oฤ‡i'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $result = mb_substr($string, 0, 2); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $result = mb_substr($string, 3, 3); $expected = '่ฎพไธบ้ฆ–'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = mb_substr($string, 0, 1); $expected = 'ไธ€'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = mb_substr($string, 6); $expected = false; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = mb_substr($string, 0); $expected = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); } /** * testMultibyteSubstr method * * @return void */ public function testMultibyteSubstr() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $result = Multibyte::substr($string, 4, 7); $expected = 'EFGHIJK'; $this->assertEquals($expected, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $result = Multibyte::substr($string, 4, 7); $expected = 'ร„ร…ร†ร‡รˆร‰รŠ'; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $result = Multibyte::substr($string, 4, 7); $expected = 'ฤˆฤŠฤŒฤŽฤฤ’ฤ”'; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = Multibyte::substr($string, 4, 7); $expected = '%&\'()*+'; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $result = Multibyte::substr($string, 4); $expected = 'ยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $result = Multibyte::substr($string, 4, 7); $expected = 'รรŽรรร‘ร’ร“'; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $result = Multibyte::substr($string, 4, 7); $expected = 'ฤฑฤฒฤณฤดฤตฤถฤท'; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $result = Multibyte::substr($string, 25); $expected = 'ฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $result = Multibyte::substr($string, 3); $expected = 'ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $result = Multibyte::substr($string, 3); $expected = 'ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $result = Multibyte::substr($string, 3, 16); $expected = 'ะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎ'; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $result = Multibyte::substr($string, 3, 6); $expected = 'ู„ู…ู†ู‡ูˆู‰'; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $result = Multibyte::substr($string, 6, 14); $expected = 'โœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒ'; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $result = Multibyte::substr($string, 8, 13); $expected = 'โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”'; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $result = Multibyte::substr($string, 12, 24); $expected = 'โฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจ'; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $result = Multibyte::substr($string, 12, 24); $expected = '๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„'; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $result = Multibyte::substr($string, 12); $expected = '๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $result = Multibyte::substr($string, 24, 12); $expected = '๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”'; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $result = Multibyte::substr($string, 11, 2); $expected = '๏ฝŒ๏ฝ'; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $result = Multibyte::substr($string, 7, 11); $expected = '๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ'; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $result = Multibyte::substr($string, 13, 13); $expected = '๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’'; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = Multibyte::substr($string, 3, 4); $expected = 'ฤผล, '; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $result = Multibyte::substr($string, 3, 4); $expected = 'lo, '; $this->assertEquals($expected, $result); $string = 'ฤini'; $result = Multibyte::substr($string, 3); $expected = 'i'; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $result = Multibyte::substr($string, 1); $expected = 'oฤ‡i'; $this->assertEquals($expected, $result); $string = 'drลพavni'; $result = Multibyte::substr($string, 0, 2); $expected = 'dr'; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $result = Multibyte::substr($string, 3, 3); $expected = '่ฎพไธบ้ฆ–'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = Multibyte::substr($string, 0, 1); $expected = 'ไธ€'; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = Multibyte::substr($string, 6); $expected = false; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = Multibyte::substr($string, 0); $expected = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $this->assertEquals($expected, $result); } /** * testMultibyteSubstr method * * @return void */ public function testMultibyteMimeEncode() { $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $result = Multibyte::mimeEncode($string); $this->assertEquals($string, $result); $string = 'ร€รร‚รƒร„ร…ร†ร‡รˆร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร˜ร™รšร›รœรรž'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?w4DDgcOCw4PDhMOFw4bDh8OIw4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5U=?=' . "\r\n" . ' =?UTF-8?B?w5bDmMOZw5rDm8Ocw53Dng==?='; $this->assertEquals($expected, $result); $result = Multibyte::mimeEncode($string, null, "\n"); $expected = '=?UTF-8?B?w4DDgcOCw4PDhMOFw4bDh8OIw4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5U=?=' . "\n" . ' =?UTF-8?B?w5bDmMOZw5rDm8Ocw53Dng==?='; $this->assertEquals($expected, $result); $string = 'ฤ€ฤ‚ฤ„ฤ†ฤˆฤŠฤŒฤŽฤฤ’ฤ”ฤ–ฤ˜ฤšฤœฤžฤ ฤขฤคฤฆฤจฤชฤฌฤฎฤฒฤดฤถฤนฤปฤฝฤฟลลƒล…ล‡ลŠลŒลŽลล’ล”ล–ล˜ลšลœลžล ลขลคลฆลจลชลฌลฎลฐลฒลดลถลนลปลฝ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?xIDEgsSExIbEiMSKxIzEjsSQxJLElMSWxJjEmsScxJ7EoMSixKTEpsSoxKo=?=' . "\r\n" . ' =?UTF-8?B?xKzErsSyxLTEtsS5xLvEvcS/xYHFg8WFxYfFisWMxY7FkMWSxZTFlsWYxZo=?=' . "\r\n" . ' =?UTF-8?B?xZzFnsWgxaLFpMWmxajFqsWsxa7FsMWyxbTFtsW5xbvFvQ==?='; $this->assertEquals($expected, $result); $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?ISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xN?=' . "\r\n" . ' =?UTF-8?B?Tk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6?=' . "\r\n" . ' =?UTF-8?B?e3x9fg==?='; $this->assertEquals($expected, $result); $string = 'ยกยขยฃยคยฅยฆยงยจยฉยชยซยฌยญยฎยฏยฐยฑยฒยณยดยตยถยทยธยนยบยปยผยฝยพยฟร€รร‚รƒร„ร…ร†ร‡รˆ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?wqHCosKjwqTCpcKmwqfCqMKpwqrCq8Kswq3CrsKvwrDCscKywrPCtMK1wrY=?=' . "\r\n" . ' =?UTF-8?B?wrfCuMK5wrrCu8K8wr3CvsK/w4DDgcOCw4PDhMOFw4bDh8OI?='; $this->assertEquals($expected, $result); $string = 'ร‰รŠร‹รŒรรŽรรร‘ร’ร“ร”ร•ร–ร—ร˜ร™รšร›รœรรžรŸร รกรขรฃรครฅรฆรงรจรฉรชรซรฌรญรฎรฏรฐรฑรฒรณรดรตรถรทรธรนรบรปรผรฝรพรฟฤ€ฤฤ‚ฤƒฤ„ฤ…ฤ†ฤ‡ฤˆฤ‰ฤŠฤ‹ฤŒฤฤŽฤฤฤ‘ฤ’ฤ“ฤ”ฤ•ฤ–ฤ—ฤ˜ฤ™ฤšฤ›ฤœฤฤžฤŸฤ ฤกฤขฤฃฤคฤฅฤฆฤงฤจฤฉฤชฤซฤฌ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?w4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5XDlsOXw5jDmcOaw5vDnMOdw54=?=' . "\r\n" . ' =?UTF-8?B?w5/DoMOhw6LDo8Okw6XDpsOnw6jDqcOqw6vDrMOtw67Dr8Oww7HDssOzw7Q=?=' . "\r\n" . ' =?UTF-8?B?w7XDtsO3w7jDucO6w7vDvMO9w77Dv8SAxIHEgsSDxITEhcSGxIfEiMSJxIo=?=' . "\r\n" . ' =?UTF-8?B?xIvEjMSNxI7Ej8SQxJHEksSTxJTElcSWxJfEmMSZxJrEm8ScxJ3EnsSfxKA=?=' . "\r\n" . ' =?UTF-8?B?xKHEosSjxKTEpcSmxKfEqMSpxKrEq8Ss?='; $this->assertEquals($expected, $result); $string = 'ฤญฤฎฤฏฤฐฤฑฤฒฤณฤดฤตฤถฤทฤธฤนฤบฤปฤผฤฝฤพฤฟล€ลล‚ลƒล„ล…ล†ล‡ลˆล‰ลŠล‹ลŒลลŽลลล‘ล’ล“ล”ล•ล–ล—ล˜ล™ลšล›ลœลลžลŸล ลกลขลฃลคลฅลฆลงลจลฉลชลซลฌลญลฎลฏลฐลฑลฒลณลดลตลถลทลธลนลบลปลผลฝลพลฟฦ€ฦฦ‚ฦƒฦ„ฦ…ฦ†ฦ‡ฦˆฦ‰ฦŠฦ‹ฦŒฦฦŽฦฦ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?xK3ErsSvxLDEscSyxLPEtMS1xLbEt8S4xLnEusS7xLzEvcS+xL/FgMWBxYI=?=' . "\r\n" . ' =?UTF-8?B?xYPFhMWFxYbFh8WIxYnFisWLxYzFjcWOxY/FkMWRxZLFk8WUxZXFlsWXxZg=?=' . "\r\n" . ' =?UTF-8?B?xZnFmsWbxZzFncWexZ/FoMWhxaLFo8WkxaXFpsWnxajFqcWqxavFrMWtxa4=?=' . "\r\n" . ' =?UTF-8?B?xa/FsMWxxbLFs8W0xbXFtsW3xbjFucW6xbvFvMW9xb7Fv8aAxoHGgsaDxoQ=?=' . "\r\n" . ' =?UTF-8?B?xoXGhsaHxojGicaKxovGjMaNxo7Gj8aQ?='; $this->assertEquals($expected, $result); $string = 'ฦ‘ฦ’ฦ“ฦ”ฦ•ฦ–ฦ—ฦ˜ฦ™ฦšฦ›ฦœฦฦžฦŸฦ ฦกฦขฦฃฦคฦฅฦฆฦงฦจฦฉฦชฦซฦฌฦญฦฎฦฏฦฐฦฑฦฒฦณฦดฦตฦถฦทฦธฦนฦบฦปฦผฦฝฦพฦฟว€วว‚วƒว„ว…ว†ว‡วˆว‰วŠว‹วŒววŽววว‘ว’ว“ว”ว•ว–ว—ว˜ว™วšว›วœววžวŸว วกวขวฃวควฅวฆวงวจวฉวชวซวฌวญวฎวฏวฐวฑวฒวณวด'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?xpHGksaTxpTGlcaWxpfGmMaZxprGm8acxp3GnsafxqDGocaixqPGpMalxqY=?=' . "\r\n" . ' =?UTF-8?B?xqfGqMapxqrGq8asxq3GrsavxrDGscayxrPGtMa1xrbGt8a4xrnGusa7xrw=?=' . "\r\n" . ' =?UTF-8?B?xr3Gvsa/x4DHgceCx4PHhMeFx4bHh8eIx4nHiseLx4zHjceOx4/HkMeRx5I=?=' . "\r\n" . ' =?UTF-8?B?x5PHlMeVx5bHl8eYx5nHmsebx5zHnceex5/HoMehx6LHo8ekx6XHpsenx6g=?=' . "\r\n" . ' =?UTF-8?B?x6nHqserx6zHrceux6/HsMexx7LHs8e0?='; $this->assertEquals($expected, $result); $string = 'ษ™ษšษ›ษœษษžษŸษ ษกษขษฃษคษฅษฆษงษจษฉษชษซษฌษญษฎษฏษฐษฑษฒษณษดษตษถษทษธษนษบษปษผษฝษพษฟส€สส‚สƒส„ส…ส†ส‡สˆส‰สŠส‹สŒสสŽสสส‘ส’ส“ส”ส•ส–ส—ส˜ส™สšส›สœสสžสŸส สกสขสฃสคสฅสฆสงสจสฉสชสซสฌสญสฎสฏสฐสฑสฒสณสดสตสถสทสธสนสบสปสผ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?yZnJmsmbyZzJncmeyZ/JoMmhyaLJo8mkyaXJpsmnyajJqcmqyavJrMmtya4=?=' . "\r\n" . ' =?UTF-8?B?ya/JsMmxybLJs8m0ybXJtsm3ybjJucm6ybvJvMm9yb7Jv8qAyoHKgsqDyoQ=?=' . "\r\n" . ' =?UTF-8?B?yoXKhsqHyojKicqKyovKjMqNyo7Kj8qQypHKksqTypTKlcqWypfKmMqZypo=?=' . "\r\n" . ' =?UTF-8?B?ypvKnMqdyp7Kn8qgyqHKosqjyqTKpcqmyqfKqMqpyqrKq8qsyq3KrsqvyrA=?=' . "\r\n" . ' =?UTF-8?B?yrHKssqzyrTKtcq2yrfKuMq5yrrKu8q8?='; $this->assertEquals($expected, $result); $string = 'ะ€ะะ‚ะƒะ„ะ…ะ†ะ‡ะˆะ‰ะŠะ‹ะŒะะŽะะะ‘ะ’ะ“ะ”ะ•ะ–ะ—ะ˜ะ™ะšะ›'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?0IDQgdCC0IPQhNCF0IbQh9CI0InQitCL0IzQjdCO0I/QkNCR0JLQk9CU0JU=?=' . "\r\n" . ' =?UTF-8?B?0JbQl9CY0JnQmtCb?='; $this->assertEquals($expected, $result); $string = 'ะœะะžะŸะ ะกะขะฃะคะฅะฆะงะจะฉะชะซะฌะญะฎะฏะฐะฑะฒะณะดะตะถะทะธะนะบะปะผะฝะพะฟั€ัั‚ัƒั„ั…ั†ั‡ัˆั‰ัŠั‹ัŒ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?0JzQndCe0J/QoNCh0KLQo9Ck0KXQptCn0KjQqdCq0KvQrNCt0K7Qr9Cw0LE=?=' . "\r\n" . ' =?UTF-8?B?0LLQs9C00LXQttC30LjQudC60LvQvNC90L7Qv9GA0YHRgtGD0YTRhdGG0Yc=?=' . "\r\n" . ' =?UTF-8?B?0YjRidGK0YvRjA==?='; $this->assertEquals($expected, $result); $string = 'ูู‚ูƒู„ู…ู†ู‡ูˆู‰ูŠู‹ูŒููŽู'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM2Y3ZjtmP?='; $this->assertEquals($expected, $result); $string = 'โœฐโœฑโœฒโœณโœดโœตโœถโœทโœธโœนโœบโœปโœผโœฝโœพโœฟโ€โโ‚โƒโ„โ…โ†โ‡โˆโ‰โŠโ‹โŒโโŽโโโ‘โ’โ“โ”โ•โ–โ—โ˜โ™โšโ›โœโโž'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?4pyw4pyx4pyy4pyz4py04py14py24py34py44py54py64py74py84py94py+?=' . "\r\n" . ' =?UTF-8?B?4py/4p2A4p2B4p2C4p2D4p2E4p2F4p2G4p2H4p2I4p2J4p2K4p2L4p2M4p2N?=' . "\r\n" . ' =?UTF-8?B?4p2O4p2P4p2Q4p2R4p2S4p2T4p2U4p2V4p2W4p2X4p2Y4p2Z4p2a4p2b4p2c?=' . "\r\n" . ' =?UTF-8?B?4p2d4p2e?='; $this->assertEquals($expected, $result); $string = 'โบ€โบโบ‚โบƒโบ„โบ…โบ†โบ‡โบˆโบ‰โบŠโบ‹โบŒโบโบŽโบโบโบ‘โบ’โบ“โบ”โบ•โบ–โบ—โบ˜โบ™โบ›โบœโบโบžโบŸโบ โบกโบขโบฃโบคโบฅโบฆโบงโบจโบฉโบชโบซโบฌโบญโบฎโบฏโบฐโบฑโบฒโบณโบดโบตโบถโบทโบธโบนโบบโบปโบผโบฝโบพโบฟโป€โปโป‚โปƒโป„โป…โป†โป‡โปˆโป‰โปŠโป‹โปŒโปโปŽโปโปโป‘โป’โป“โป”โป•โป–โป—โป˜โป™โปšโป›โปœโปโปžโปŸโป '; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?4rqA4rqB4rqC4rqD4rqE4rqF4rqG4rqH4rqI4rqJ4rqK4rqL4rqM4rqN4rqO?=' . "\r\n" . ' =?UTF-8?B?4rqP4rqQ4rqR4rqS4rqT4rqU4rqV4rqW4rqX4rqY4rqZ4rqb4rqc4rqd4rqe?=' . "\r\n" . ' =?UTF-8?B?4rqf4rqg4rqh4rqi4rqj4rqk4rql4rqm4rqn4rqo4rqp4rqq4rqr4rqs4rqt?=' . "\r\n" . ' =?UTF-8?B?4rqu4rqv4rqw4rqx4rqy4rqz4rq04rq14rq24rq34rq44rq54rq64rq74rq8?=' . "\r\n" . ' =?UTF-8?B?4rq94rq+4rq/4ruA4ruB4ruC4ruD4ruE4ruF4ruG4ruH4ruI4ruJ4ruK4ruL?=' . "\r\n" . ' =?UTF-8?B?4ruM4ruN4ruO4ruP4ruQ4ruR4ruS4ruT4ruU4ruV4ruW4ruX4ruY4ruZ4rua?=' . "\r\n" . ' =?UTF-8?B?4rub4ruc4rud4rue4ruf4rug?='; $this->assertEquals($expected, $result); $string = 'โฝ…โฝ†โฝ‡โฝˆโฝ‰โฝŠโฝ‹โฝŒโฝโฝŽโฝโฝโฝ‘โฝ’โฝ“โฝ”โฝ•โฝ–โฝ—โฝ˜โฝ™โฝšโฝ›โฝœโฝโฝžโฝŸโฝ โฝกโฝขโฝฃโฝคโฝฅโฝฆโฝงโฝจโฝฉโฝชโฝซโฝฌโฝญโฝฎโฝฏโฝฐโฝฑโฝฒโฝณโฝดโฝตโฝถโฝทโฝธโฝนโฝบโฝปโฝผโฝฝโฝพโฝฟ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?4r2F4r2G4r2H4r2I4r2J4r2K4r2L4r2M4r2N4r2O4r2P4r2Q4r2R4r2S4r2T?=' . "\r\n" . ' =?UTF-8?B?4r2U4r2V4r2W4r2X4r2Y4r2Z4r2a4r2b4r2c4r2d4r2e4r2f4r2g4r2h4r2i?=' . "\r\n" . ' =?UTF-8?B?4r2j4r2k4r2l4r2m4r2n4r2o4r2p4r2q4r2r4r2s4r2t4r2u4r2v4r2w4r2x?=' . "\r\n" . ' =?UTF-8?B?4r2y4r2z4r204r214r224r234r244r254r264r274r284r294r2+4r2/?='; $this->assertEquals($expected, $result); $string = '๋ˆก๋ˆข๋ˆฃ๋ˆค๋ˆฅ๋ˆฆ๋ˆง๋ˆจ๋ˆฉ๋ˆช๋ˆซ๋ˆฌ๋ˆญ๋ˆฎ๋ˆฏ๋ˆฐ๋ˆฑ๋ˆฒ๋ˆณ๋ˆด๋ˆต๋ˆถ๋ˆท๋ˆธ๋ˆน๋ˆบ๋ˆป๋ˆผ๋ˆฝ๋ˆพ๋ˆฟ๋‰€๋‰๋‰‚๋‰ƒ๋‰„๋‰…๋‰†๋‰‡๋‰ˆ๋‰‰๋‰Š๋‰‹๋‰Œ๋‰๋‰Ž๋‰๋‰๋‰‘๋‰’๋‰“๋‰”๋‰•๋‰–๋‰—๋‰˜๋‰™๋‰š๋‰›๋‰œ๋‰๋‰ž๋‰Ÿ๋‰ ๋‰ก๋‰ข๋‰ฃ๋‰ค๋‰ฅ๋‰ฆ๋‰ง๋‰จ๋‰ฉ๋‰ช๋‰ซ๋‰ฌ๋‰ญ๋‰ฎ๋‰ฏ๋‰ฐ๋‰ฑ๋‰ฒ๋‰ณ๋‰ด๋‰ต๋‰ถ๋‰ท๋‰ธ๋‰น๋‰บ๋‰ป๋‰ผ๋‰ฝ๋‰พ๋‰ฟ๋Š€๋Š๋Š‚๋Šƒ๋Š„'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?64ih64ii64ij64ik64il64im64in64io64ip64iq64ir64is64it64iu64iv?=' . "\r\n" . ' =?UTF-8?B?64iw64ix64iy64iz64i064i164i264i364i464i564i664i764i864i964i+?=' . "\r\n" . ' =?UTF-8?B?64i/64mA64mB64mC64mD64mE64mF64mG64mH64mI64mJ64mK64mL64mM64mN?=' . "\r\n" . ' =?UTF-8?B?64mO64mP64mQ64mR64mS64mT64mU64mV64mW64mX64mY64mZ64ma64mb64mc?=' . "\r\n" . ' =?UTF-8?B?64md64me64mf64mg64mh64mi64mj64mk64ml64mm64mn64mo64mp64mq64mr?=' . "\r\n" . ' =?UTF-8?B?64ms64mt64mu64mv64mw64mx64my64mz64m064m164m264m364m464m564m6?=' . "\r\n" . ' =?UTF-8?B?64m764m864m964m+64m/64qA64qB64qC64qD64qE?='; $this->assertEquals($expected, $result); $string = '๏นฐ๏นฑ๏นฒ๏นณ๏นด๏นต๏นถ๏นท๏นธ๏นน๏นบ๏นป๏นผ๏นฝ๏นพ๏นฟ๏บ€๏บ๏บ‚๏บƒ๏บ„๏บ…๏บ†๏บ‡๏บˆ๏บ‰๏บŠ๏บ‹๏บŒ๏บ๏บŽ๏บ๏บ๏บ‘๏บ’๏บ“๏บ”๏บ•๏บ–๏บ—๏บ˜๏บ™๏บš๏บ›๏บœ๏บ๏บž๏บŸ๏บ ๏บก๏บข๏บฃ๏บค๏บฅ๏บฆ๏บง๏บจ๏บฉ๏บช๏บซ๏บฌ๏บญ๏บฎ๏บฏ๏บฐ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?77mw77mx77my77mz77m077m177m277m377m477m577m677m777m877m977m+?=' . "\r\n" . ' =?UTF-8?B?77m/77qA77qB77qC77qD77qE77qF77qG77qH77qI77qJ77qK77qL77qM77qN?=' . "\r\n" . ' =?UTF-8?B?77qO77qP77qQ77qR77qS77qT77qU77qV77qW77qX77qY77qZ77qa77qb77qc?=' . "\r\n" . ' =?UTF-8?B?77qd77qe77qf77qg77qh77qi77qj77qk77ql77qm77qn77qo77qp77qq77qr?=' . "\r\n" . ' =?UTF-8?B?77qs77qt77qu77qv77qw?='; $this->assertEquals($expected, $result); $string = '๏บฑ๏บฒ๏บณ๏บด๏บต๏บถ๏บท๏บธ๏บน๏บบ๏บป๏บผ๏บฝ๏บพ๏บฟ๏ป€๏ป๏ป‚๏ปƒ๏ป„๏ป…๏ป†๏ป‡๏ปˆ๏ป‰๏ปŠ๏ป‹๏ปŒ๏ป๏ปŽ๏ป๏ป๏ป‘๏ป’๏ป“๏ป”๏ป•๏ป–๏ป—๏ป˜๏ป™๏ปš๏ป›๏ปœ๏ป๏ปž๏ปŸ๏ป ๏ปก๏ปข๏ปฃ๏ปค๏ปฅ๏ปฆ๏ปง๏ปจ๏ปฉ๏ปช๏ปซ๏ปฌ๏ปญ๏ปฎ๏ปฏ๏ปฐ๏ปฑ๏ปฒ๏ปณ๏ปด๏ปต๏ปถ๏ปท๏ปธ๏ปน๏ปบ๏ปป๏ปผ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?77qx77qy77qz77q077q177q277q377q477q577q677q777q877q977q+77q/?=' . "\r\n" . ' =?UTF-8?B?77uA77uB77uC77uD77uE77uF77uG77uH77uI77uJ77uK77uL77uM77uN77uO?=' . "\r\n" . ' =?UTF-8?B?77uP77uQ77uR77uS77uT77uU77uV77uW77uX77uY77uZ77ua77ub77uc77ud?=' . "\r\n" . ' =?UTF-8?B?77ue77uf77ug77uh77ui77uj77uk77ul77um77un77uo77up77uq77ur77us?=' . "\r\n" . ' =?UTF-8?B?77ut77uu77uv77uw77ux77uy77uz77u077u177u277u377u477u577u677u7?=' . "\r\n" . ' =?UTF-8?B?77u8?='; $this->assertEquals($expected, $result); $string = '๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?772B772C772D772E772F772G772H772I772J772K772L772M772N772O772P?=' . "\r\n" . ' =?UTF-8?B?772Q772R772S772T772U772V772W772X772Y772Z772a?='; $this->assertEquals($expected, $result); $string = '๏ฝก๏ฝข๏ฝฃ๏ฝค๏ฝฅ๏ฝฆ๏ฝง๏ฝจ๏ฝฉ๏ฝช๏ฝซ๏ฝฌ๏ฝญ๏ฝฎ๏ฝฏ๏ฝฐ๏ฝฑ๏ฝฒ๏ฝณ๏ฝด๏ฝต๏ฝถ๏ฝท๏ฝธ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?772h772i772j772k772l772m772n772o772p772q772r772s772t772u772v?=' . "\r\n" . ' =?UTF-8?B?772w772x772y772z77207721772277237724?='; $this->assertEquals($expected, $result); $string = '๏ฝน๏ฝบ๏ฝป๏ฝผ๏ฝฝ๏ฝพ๏ฝฟ๏พ€๏พ๏พ‚๏พƒ๏พ„๏พ…๏พ†๏พ‡๏พˆ๏พ‰๏พŠ๏พ‹๏พŒ๏พ๏พŽ๏พ๏พ๏พ‘๏พ’๏พ“๏พ”๏พ•๏พ–๏พ—๏พ˜๏พ™๏พš๏พ›๏พœ๏พ๏พž'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?77257726772777287729772+772/776A776B776C776D776E776F776G776H?=' . "\r\n" . ' =?UTF-8?B?776I776J776K776L776M776N776O776P776Q776R776S776T776U776V776W?=' . "\r\n" . ' =?UTF-8?B?776X776Y776Z776a776b776c776d776e?='; $this->assertEquals($expected, $result); $string = 'ฤคฤ“ฤบฤผล, ลดล‘ล™ฤผฤ!'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?xKTEk8S6xLzFjywgxbTFkcWZxLzEjyE=?='; $this->assertEquals($expected, $result); $string = 'Hello, World!'; $result = Multibyte::mimeEncode($string); $this->assertEquals($string, $result); $string = 'ฤini'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?xI1pbmk=?='; $this->assertEquals($expected, $result); $string = 'moฤ‡i'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?bW/Eh2k=?='; $this->assertEquals($expected, $result); $string = 'drลพavni'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?ZHLFvmF2bmk=?='; $this->assertEquals($expected, $result); $string = 'ๆŠŠ็™พๅบฆ่ฎพไธบ้ฆ–้กต'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?5oqK55m+5bqm6K6+5Li66aaW6aG1?='; $this->assertEquals($expected, $result); $string = 'ไธ€ไบŒไธ‰ๅ‘จๆฐธ้พ'; $result = Multibyte::mimeEncode($string); $expected = '=?UTF-8?B?5LiA5LqM5LiJ5ZGo5rC46b6N?='; $this->assertEquals($expected, $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/I18n/L10nTest.php0000644000000000000000000013171313365153155021717 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.I18n * @since CakePHP(tm) v 1.2.0.5432 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('L10n', 'I18n'); /** * L10nTest class * * @package Cake.Test.Case.I18n */ class L10nTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); Configure::delete('Config.language'); } /** * testGet method * * @return void */ public function testGet() { $localize = new L10n(); // Catalog Entry $lang = $localize->get('en'); $this->assertEquals('en', $lang); $this->assertEquals('English', $localize->language); $this->assertEquals(array('eng'), $localize->languagePath); $this->assertEquals('eng', $localize->locale); // Map Entry $localize->get('eng'); $this->assertEquals('English', $localize->language); $this->assertEquals(array('eng'), $localize->languagePath); $this->assertEquals('eng', $localize->locale); // Catalog Entry $localize->get('en-ca'); $this->assertEquals('English (Canadian)', $localize->language); $this->assertEquals(array('en_ca', 'eng'), $localize->languagePath); $this->assertEquals('en_ca', $localize->locale); // Default Entry $localize->default = 'en-us'; $lang = $localize->get('use_default'); $this->assertEquals('en-us', $lang); $this->assertEquals('English (United States)', $localize->language); $this->assertEquals(array('en_us', 'eng'), $localize->languagePath); $this->assertEquals('en_us', $localize->locale); $localize->get('es'); $localize->get(''); $this->assertEquals('en-us', $localize->lang); } /** * testGetAutoLanguage method * * @return void */ public function testGetAutoLanguage() { $serverBackup = $_SERVER; $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'inexistent,en-ca'; $localize = new L10n(); $lang = $localize->get(); $this->assertEquals('en-ca', $lang); $this->assertEquals('English (Canadian)', $localize->language); $this->assertEquals(array('en_ca', 'eng'), $localize->languagePath); $this->assertEquals('en_ca', $localize->locale); $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'es_mx'; $lang = $localize->get(); $this->assertEquals('es-mx', $lang); $this->assertEquals('Spanish (Mexican)', $localize->language); $this->assertEquals(array('es_mx', 'spa'), $localize->languagePath); $this->assertEquals('es_mx', $localize->locale); $localize = new L10n(); $localize->default = 'en-us'; $lang = $localize->get(); $this->assertEquals(array('es_mx', 'spa', 'eng'), $localize->languagePath); $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en_xy,en_ca'; $localize->get(); $this->assertEquals('English', $localize->language); $this->assertEquals(array('eng'), $localize->languagePath); $this->assertEquals('eng', $localize->locale); $_SERVER = $serverBackup; } /** * testGet method with deprecated constant DEFAULT_LANGUAGE * * @return void */ public function testGetWithDeprecatedConstant() { $this->skipIf(defined('DEFAULT_LANGUAGE'), 'Cannot re-define already defined constant.'); define('DEFAULT_LANGUAGE', 'en-us'); $localize = new L10n(); $lang = $localize->get('use_default'); $this->assertEquals('en-us', $lang); $this->assertEquals('English (United States)', $localize->language); $this->assertEquals(array('en_us', 'eng'), $localize->languagePath); $this->assertEquals('en_us', $localize->locale); $localize = new L10n(); $lang = $localize->get(); $this->assertEquals('en-us', $lang); $this->assertEquals('English (United States)', $localize->language); $this->assertEquals(array('en_us', 'eng'), $localize->languagePath); $this->assertEquals('en_us', $localize->locale); } /** * testMap method * * @return void */ public function testMap() { $localize = new L10n(); $result = $localize->map(array('afr', 'af')); $expected = array('afr' => 'af', 'af' => 'afr'); $this->assertEquals($expected, $result); $result = $localize->map(array('sqi', 'sq')); $expected = array('sqi' => 'sq', 'sq' => 'sqi'); $this->assertEquals($expected, $result); $result = $localize->map(array('alb', 'sq')); $expected = array('alb' => 'sq', 'sq' => 'sqi'); $this->assertEquals($expected, $result); $result = $localize->map(array('ara', 'ar')); $expected = array('ara' => 'ar', 'ar' => 'ara'); $this->assertEquals($expected, $result); $result = $localize->map(array('hye', 'hy')); $expected = array('hye' => 'hy', 'hy' => 'hye'); $this->assertEquals($expected, $result); $result = $localize->map(array('eus', 'eu')); $expected = array('eus' => 'eu', 'eu' => 'eus'); $this->assertEquals($expected, $result); $result = $localize->map(array('baq', 'eu')); $expected = array('baq' => 'eu', 'eu' => 'eus'); $this->assertEquals($expected, $result); $result = $localize->map(array('bos', 'bs')); $expected = array('bos' => 'bs', 'bs' => 'bos'); $this->assertEquals($expected, $result); $result = $localize->map(array('bul', 'bg')); $expected = array('bul' => 'bg', 'bg' => 'bul'); $this->assertEquals($expected, $result); $result = $localize->map(array('bel', 'be')); $expected = array('bel' => 'be', 'be' => 'bel'); $this->assertEquals($expected, $result); $result = $localize->map(array('cat', 'ca')); $expected = array('cat' => 'ca', 'ca' => 'cat'); $this->assertEquals($expected, $result); $result = $localize->map(array('chi', 'zh')); $expected = array('chi' => 'zh', 'zh' => 'zho'); $this->assertEquals($expected, $result); $result = $localize->map(array('zho', 'zh')); $expected = array('zho' => 'zh', 'zh' => 'zho'); $this->assertEquals($expected, $result); $result = $localize->map(array('hrv', 'hr')); $expected = array('hrv' => 'hr', 'hr' => 'hrv'); $this->assertEquals($expected, $result); $result = $localize->map(array('ces', 'cs')); $expected = array('ces' => 'cs', 'cs' => 'ces'); $this->assertEquals($expected, $result); $result = $localize->map(array('cze', 'cs')); $expected = array('cze' => 'cs', 'cs' => 'ces'); $this->assertEquals($expected, $result); $result = $localize->map(array('dan', 'da')); $expected = array('dan' => 'da', 'da' => 'dan'); $this->assertEquals($expected, $result); $result = $localize->map(array('dut', 'nl')); $expected = array('dut' => 'nl', 'nl' => 'nld'); $this->assertEquals($expected, $result); $result = $localize->map(array('nld', 'nl')); $expected = array('nld' => 'nl', 'nl' => 'nld'); $this->assertEquals($expected, $result); $result = $localize->map(array('nld')); $expected = array('nld' => 'nl'); $this->assertEquals($expected, $result); $result = $localize->map(array('dut')); $expected = array('dut' => 'nl'); $this->assertEquals($expected, $result); $result = $localize->map(array('eng', 'en')); $expected = array('eng' => 'en', 'en' => 'eng'); $this->assertEquals($expected, $result); $result = $localize->map(array('est', 'et')); $expected = array('est' => 'et', 'et' => 'est'); $this->assertEquals($expected, $result); $result = $localize->map(array('fao', 'fo')); $expected = array('fao' => 'fo', 'fo' => 'fao'); $this->assertEquals($expected, $result); $result = $localize->map(array('fas', 'fa')); $expected = array('fas' => 'fa', 'fa' => 'fas'); $this->assertEquals($expected, $result); $result = $localize->map(array('per', 'fa')); $expected = array('per' => 'fa', 'fa' => 'fas'); $this->assertEquals($expected, $result); $result = $localize->map(array('fin', 'fi')); $expected = array('fin' => 'fi', 'fi' => 'fin'); $this->assertEquals($expected, $result); $result = $localize->map(array('fra', 'fr')); $expected = array('fra' => 'fr', 'fr' => 'fra'); $this->assertEquals($expected, $result); $result = $localize->map(array('fre', 'fr')); $expected = array('fre' => 'fr', 'fr' => 'fra'); $this->assertEquals($expected, $result); $result = $localize->map(array('gla', 'gd')); $expected = array('gla' => 'gd', 'gd' => 'gla'); $this->assertEquals($expected, $result); $result = $localize->map(array('glg', 'gl')); $expected = array('glg' => 'gl', 'gl' => 'glg'); $this->assertEquals($expected, $result); $result = $localize->map(array('deu', 'de')); $expected = array('deu' => 'de', 'de' => 'deu'); $this->assertEquals($expected, $result); $result = $localize->map(array('ger', 'de')); $expected = array('ger' => 'de', 'de' => 'deu'); $this->assertEquals($expected, $result); $result = $localize->map(array('ell', 'el')); $expected = array('ell' => 'el', 'el' => 'gre'); $this->assertEquals($expected, $result); $result = $localize->map(array('gre', 'el')); $expected = array('gre' => 'el', 'el' => 'gre'); $this->assertEquals($expected, $result); $result = $localize->map(array('heb', 'he')); $expected = array('heb' => 'he', 'he' => 'heb'); $this->assertEquals($expected, $result); $result = $localize->map(array('hin', 'hi')); $expected = array('hin' => 'hi', 'hi' => 'hin'); $this->assertEquals($expected, $result); $result = $localize->map(array('hun', 'hu')); $expected = array('hun' => 'hu', 'hu' => 'hun'); $this->assertEquals($expected, $result); $result = $localize->map(array('ice', 'is')); $expected = array('ice' => 'is', 'is' => 'isl'); $this->assertEquals($expected, $result); $result = $localize->map(array('isl', 'is')); $expected = array('isl' => 'is', 'is' => 'isl'); $this->assertEquals($expected, $result); $result = $localize->map(array('ind', 'id')); $expected = array('ind' => 'id', 'id' => 'ind'); $this->assertEquals($expected, $result); $result = $localize->map(array('gle', 'ga')); $expected = array('gle' => 'ga', 'ga' => 'gle'); $this->assertEquals($expected, $result); $result = $localize->map(array('ita', 'it')); $expected = array('ita' => 'it', 'it' => 'ita'); $this->assertEquals($expected, $result); $result = $localize->map(array('jpn', 'ja')); $expected = array('jpn' => 'ja', 'ja' => 'jpn'); $this->assertEquals($expected, $result); $result = $localize->map(array('kaz', 'kk')); $expected = array('kaz' => 'kk', 'kk' => 'kaz'); $this->assertEquals($expected, $result); $result = $localize->map(array('kor', 'ko')); $expected = array('kor' => 'ko', 'ko' => 'kor'); $this->assertEquals($expected, $result); $result = $localize->map(array('lav', 'lv')); $expected = array('lav' => 'lv', 'lv' => 'lav'); $this->assertEquals($expected, $result); $result = $localize->map(array('lit', 'lt')); $expected = array('lit' => 'lt', 'lt' => 'lit'); $this->assertEquals($expected, $result); $result = $localize->map(array('mac', 'mk')); $expected = array('mac' => 'mk', 'mk' => 'mkd'); $this->assertEquals($expected, $result); $result = $localize->map(array('mkd', 'mk')); $expected = array('mkd' => 'mk', 'mk' => 'mkd'); $this->assertEquals($expected, $result); $result = $localize->map(array('may', 'ms')); $expected = array('may' => 'ms', 'ms' => 'msa'); $this->assertEquals($expected, $result); $result = $localize->map(array('msa', 'ms')); $expected = array('msa' => 'ms', 'ms' => 'msa'); $this->assertEquals($expected, $result); $result = $localize->map(array('mlt', 'mt')); $expected = array('mlt' => 'mt', 'mt' => 'mlt'); $this->assertEquals($expected, $result); $result = $localize->map(array('nor', 'no')); $expected = array('nor' => 'no', 'no' => 'nor'); $this->assertEquals($expected, $result); $result = $localize->map(array('nob', 'nb')); $expected = array('nob' => 'nb', 'nb' => 'nob'); $this->assertEquals($expected, $result); $result = $localize->map(array('nno', 'nn')); $expected = array('nno' => 'nn', 'nn' => 'nno'); $this->assertEquals($expected, $result); $result = $localize->map(array('pol', 'pl')); $expected = array('pol' => 'pl', 'pl' => 'pol'); $this->assertEquals($expected, $result); $result = $localize->map(array('por', 'pt')); $expected = array('por' => 'pt', 'pt' => 'por'); $this->assertEquals($expected, $result); $result = $localize->map(array('roh', 'rm')); $expected = array('roh' => 'rm', 'rm' => 'roh'); $this->assertEquals($expected, $result); $result = $localize->map(array('ron', 'ro')); $expected = array('ron' => 'ro', 'ro' => 'ron'); $this->assertEquals($expected, $result); $result = $localize->map(array('rum', 'ro')); $expected = array('rum' => 'ro', 'ro' => 'ron'); $this->assertEquals($expected, $result); $result = $localize->map(array('rus', 'ru')); $expected = array('rus' => 'ru', 'ru' => 'rus'); $this->assertEquals($expected, $result); $result = $localize->map(array('sme', 'se')); $expected = array('sme' => 'se', 'se' => 'sme'); $this->assertEquals($expected, $result); $result = $localize->map(array('srp', 'sr')); $expected = array('srp' => 'sr', 'sr' => 'srp'); $this->assertEquals($expected, $result); $result = $localize->map(array('slk', 'sk')); $expected = array('slk' => 'sk', 'sk' => 'slk'); $this->assertEquals($expected, $result); $result = $localize->map(array('slo', 'sk')); $expected = array('slo' => 'sk', 'sk' => 'slk'); $this->assertEquals($expected, $result); $result = $localize->map(array('slv', 'sl')); $expected = array('slv' => 'sl', 'sl' => 'slv'); $this->assertEquals($expected, $result); $result = $localize->map(array('wen', 'sb')); $expected = array('wen' => 'sb', 'sb' => 'wen'); $this->assertEquals($expected, $result); $result = $localize->map(array('spa', 'es')); $expected = array('spa' => 'es', 'es' => 'spa'); $this->assertEquals($expected, $result); $result = $localize->map(array('swe', 'sv')); $expected = array('swe' => 'sv', 'sv' => 'swe'); $this->assertEquals($expected, $result); $result = $localize->map(array('tha', 'th')); $expected = array('tha' => 'th', 'th' => 'tha'); $this->assertEquals($expected, $result); $result = $localize->map(array('tso', 'ts')); $expected = array('tso' => 'ts', 'ts' => 'tso'); $this->assertEquals($expected, $result); $result = $localize->map(array('tsn', 'tn')); $expected = array('tsn' => 'tn', 'tn' => 'tsn'); $this->assertEquals($expected, $result); $result = $localize->map(array('tur', 'tr')); $expected = array('tur' => 'tr', 'tr' => 'tur'); $this->assertEquals($expected, $result); $result = $localize->map(array('ukr', 'uk')); $expected = array('ukr' => 'uk', 'uk' => 'ukr'); $this->assertEquals($expected, $result); $result = $localize->map(array('urd', 'ur')); $expected = array('urd' => 'ur', 'ur' => 'urd'); $this->assertEquals($expected, $result); $result = $localize->map(array('ven', 've')); $expected = array('ven' => 've', 've' => 'ven'); $this->assertEquals($expected, $result); $result = $localize->map(array('vie', 'vi')); $expected = array('vie' => 'vi', 'vi' => 'vie'); $this->assertEquals($expected, $result); $result = $localize->map(array('xho', 'xh')); $expected = array('xho' => 'xh', 'xh' => 'xho'); $this->assertEquals($expected, $result); $result = $localize->map(array('cy', 'cym')); $expected = array('cym' => 'cy', 'cy' => 'cym'); $this->assertEquals($expected, $result); $result = $localize->map(array('yid', 'yi')); $expected = array('yid' => 'yi', 'yi' => 'yid'); $this->assertEquals($expected, $result); $result = $localize->map(array('zul', 'zu')); $expected = array('zul' => 'zu', 'zu' => 'zul'); $this->assertEquals($expected, $result); } /** * testCatalog method * * @return void */ public function testCatalog() { $localize = new L10n(); $result = $localize->catalog(array('af')); $expected = array( 'af' => array('language' => 'Afrikaans', 'locale' => 'afr', 'localeFallback' => 'afr', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ar', 'ar-ae', 'ar-bh', 'ar-dz', 'ar-eg', 'ar-iq', 'ar-jo', 'ar-kw', 'ar-lb', 'ar-ly', 'ar-ma', 'ar-om', 'ar-qa', 'ar-sa', 'ar-sy', 'ar-tn', 'ar-ye')); $expected = array( 'ar' => array('language' => 'Arabic', 'locale' => 'ara', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-ae' => array('language' => 'Arabic (U.A.E.)', 'locale' => 'ar_ae', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-bh' => array('language' => 'Arabic (Bahrain)', 'locale' => 'ar_bh', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-dz' => array('language' => 'Arabic (Algeria)', 'locale' => 'ar_dz', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-eg' => array('language' => 'Arabic (Egypt)', 'locale' => 'ar_eg', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-iq' => array('language' => 'Arabic (Iraq)', 'locale' => 'ar_iq', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-jo' => array('language' => 'Arabic (Jordan)', 'locale' => 'ar_jo', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-kw' => array('language' => 'Arabic (Kuwait)', 'locale' => 'ar_kw', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-ly' => array('language' => 'Arabic (Libya)', 'locale' => 'ar_ly', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-ma' => array('language' => 'Arabic (Morocco)', 'locale' => 'ar_ma', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-om' => array('language' => 'Arabic (Oman)', 'locale' => 'ar_om', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-qa' => array('language' => 'Arabic (Qatar)', 'locale' => 'ar_qa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-sa' => array('language' => 'Arabic (Saudi Arabia)', 'locale' => 'ar_sa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-sy' => array('language' => 'Arabic (Syria)', 'locale' => 'ar_sy', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-tn' => array('language' => 'Arabic (Tunisia)', 'locale' => 'ar_tn', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'ar-ye' => array('language' => 'Arabic (Yemen)', 'locale' => 'ar_ye', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('be')); $expected = array( 'be' => array('language' => 'Byelorussian', 'locale' => 'bel', 'localeFallback' => 'bel', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('bg')); $expected = array( 'bg' => array('language' => 'Bulgarian', 'locale' => 'bul', 'localeFallback' => 'bul', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('bs')); $expected = array( 'bs' => array('language' => 'Bosnian', 'locale' => 'bos', 'localeFallback' => 'bos', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ca')); $expected = array( 'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('cs')); $expected = array( 'cs' => array('language' => 'Czech', 'locale' => 'ces', 'localeFallback' => 'ces', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('da')); $expected = array( 'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('de', 'de-at', 'de-ch', 'de-de', 'de-li', 'de-lu')); $expected = array( 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('el')); $expected = array( 'el' => array('language' => 'Greek', 'locale' => 'ell', 'localeFallback' => 'ell', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('en', 'en-au', 'en-bz', 'en-ca', 'en-gb', 'en-ie', 'en-jm', 'en-nz', 'en-tt', 'en-us', 'en-za')); $expected = array( 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-au' => array('language' => 'English (Australian)', 'locale' => 'en_au', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-bz' => array('language' => 'English (Belize)', 'locale' => 'en_bz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-ca' => array('language' => 'English (Canadian)', 'locale' => 'en_ca', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-gb' => array('language' => 'English (British)', 'locale' => 'en_gb', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-ie' => array('language' => 'English (Ireland)', 'locale' => 'en_ie', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-jm' => array('language' => 'English (Jamaica)', 'locale' => 'en_jm', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-tt' => array('language' => 'English (Trinidad)', 'locale' => 'en_tt', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-us' => array('language' => 'English (United States)', 'locale' => 'en_us', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-za' => array('language' => 'English (South Africa)', 'locale' => 'en_za', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('es', 'es-ar', 'es-bo', 'es-cl', 'es-co', 'es-cr', 'es-do', 'es-ec', 'es-es', 'es-gt', 'es-hn', 'es-mx', 'es-ni', 'es-pa', 'es-pe', 'es-pr', 'es-py', 'es-sv', 'es-uy', 'es-ve')); $expected = array( 'es' => array('language' => 'Spanish (Spain - Traditional)', 'locale' => 'spa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-ar' => array('language' => 'Spanish (Argentina)', 'locale' => 'es_ar', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-bo' => array('language' => 'Spanish (Bolivia)', 'locale' => 'es_bo', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-cl' => array('language' => 'Spanish (Chile)', 'locale' => 'es_cl', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-co' => array('language' => 'Spanish (Colombia)', 'locale' => 'es_co', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-cr' => array('language' => 'Spanish (Costa Rica)', 'locale' => 'es_cr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-ec' => array('language' => 'Spanish (Ecuador)', 'locale' => 'es_ec', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-es' => array('language' => 'Spanish (Spain)', 'locale' => 'es_es', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-gt' => array('language' => 'Spanish (Guatemala)', 'locale' => 'es_gt', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-hn' => array('language' => 'Spanish (Honduras)', 'locale' => 'es_hn', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-mx' => array('language' => 'Spanish (Mexican)', 'locale' => 'es_mx', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-ni' => array('language' => 'Spanish (Nicaragua)', 'locale' => 'es_ni', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-pa' => array('language' => 'Spanish (Panama)', 'locale' => 'es_pa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-pe' => array('language' => 'Spanish (Peru)', 'locale' => 'es_pe', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-pr' => array('language' => 'Spanish (Puerto Rico)', 'locale' => 'es_pr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-py' => array('language' => 'Spanish (Paraguay)', 'locale' => 'es_py', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-sv' => array('language' => 'Spanish (El Salvador)', 'locale' => 'es_sv', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-uy' => array('language' => 'Spanish (Uruguay)', 'locale' => 'es_uy', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('et')); $expected = array( 'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('eu')); $expected = array( 'eu' => array('language' => 'Basque', 'locale' => 'eus', 'localeFallback' => 'eus', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('fa')); $expected = array( 'fa' => array('language' => 'Farsi', 'locale' => 'fas', 'localeFallback' => 'fas', 'charset' => 'utf-8', 'direction' => 'rtl') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('fi')); $expected = array( 'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('fo')); $expected = array( 'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('fr', 'fr-be', 'fr-ca', 'fr-ch', 'fr-fr', 'fr-lu')); $expected = array( 'fr' => array('language' => 'French (Standard)', 'locale' => 'fra', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), 'fr-be' => array('language' => 'French (Belgium)', 'locale' => 'fr_be', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), 'fr-ca' => array('language' => 'French (Canadian)', 'locale' => 'fr_ca', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), 'fr-ch' => array('language' => 'French (Swiss)', 'locale' => 'fr_ch', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), 'fr-fr' => array('language' => 'French (France)', 'locale' => 'fr_fr', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), 'fr-lu' => array('language' => 'French (Luxembourg)', 'locale' => 'fr_lu', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ga')); $expected = array( 'ga' => array('language' => 'Irish', 'locale' => 'gle', 'localeFallback' => 'gle', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('gd', 'gd-ie')); $expected = array( 'gd' => array('language' => 'Gaelic (Scots)', 'locale' => 'gla', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), 'gd-ie' => array('language' => 'Gaelic (Irish)', 'locale' => 'gd_ie', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('gl')); $expected = array( 'gl' => array('language' => 'Galician', 'locale' => 'glg', 'localeFallback' => 'glg', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('he')); $expected = array( 'he' => array('language' => 'Hebrew', 'locale' => 'heb', 'localeFallback' => 'heb', 'charset' => 'utf-8', 'direction' => 'rtl') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('hi')); $expected = array( 'hi' => array('language' => 'Hindi', 'locale' => 'hin', 'localeFallback' => 'hin', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('hr')); $expected = array( 'hr' => array('language' => 'Croatian', 'locale' => 'hrv', 'localeFallback' => 'hrv', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('hu')); $expected = array( 'hu' => array('language' => 'Hungarian', 'locale' => 'hun', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('hy')); $expected = array( 'hy' => array('language' => 'Armenian - Armenia', 'locale' => 'hye', 'localeFallback' => 'hye', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('id')); $expected = array( 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('is')); $expected = array( 'is' => array('language' => 'Icelandic', 'locale' => 'isl', 'localeFallback' => 'isl', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('it', 'it-ch')); $expected = array( 'it' => array('language' => 'Italian', 'locale' => 'ita', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), 'it-ch' => array('language' => 'Italian (Swiss) ', 'locale' => 'it_ch', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ja')); $expected = array( 'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('kk')); $expected = array( 'kk' => array('language' => 'Kazakh', 'locale' => 'kaz', 'localeFallback' => 'kaz', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ko', 'ko-kp', 'ko-kr')); $expected = array( 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), 'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), 'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('koi8-r', 'ru', 'ru-mo')); $expected = array( 'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r', 'direction' => 'ltr'), 'ru' => array('language' => 'Russian', 'locale' => 'rus', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), 'ru-mo' => array('language' => 'Russian (Moldavia)', 'locale' => 'ru_mo', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('lt')); $expected = array( 'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('lv')); $expected = array( 'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('mk', 'mk-mk')); $expected = array( 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mkd', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ms')); $expected = array( 'ms' => array('language' => 'Malaysian', 'locale' => 'msa', 'localeFallback' => 'msa', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('mt')); $expected = array( 'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('nl', 'nl-be')); $expected = array( 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog('nl'); $expected = array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'); $this->assertEquals($expected, $result); $result = $localize->catalog('nld'); $expected = array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'); $this->assertEquals($expected, $result); $result = $localize->catalog('dut'); $expected = array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'); $this->assertEquals($expected, $result); $result = $localize->catalog(array('nb')); $expected = array( 'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('nn', 'no')); $expected = array( 'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), 'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('pl')); $expected = array( 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('pt', 'pt-br')); $expected = array( 'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('rm')); $expected = array( 'rm' => array('language' => 'Rhaeto-Romanic', 'locale' => 'roh', 'localeFallback' => 'roh', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ro', 'ro-mo')); $expected = array( 'ro' => array('language' => 'Romanian', 'locale' => 'ron', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), 'ro-mo' => array('language' => 'Romanian (Moldavia)', 'locale' => 'ro_mo', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('sb')); $expected = array( 'sb' => array('language' => 'Sorbian', 'locale' => 'wen', 'localeFallback' => 'wen', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('sk')); $expected = array( 'sk' => array('language' => 'Slovak', 'locale' => 'slk', 'localeFallback' => 'slk', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('sl')); $expected = array( 'sl' => array('language' => 'Slovenian', 'locale' => 'slv', 'localeFallback' => 'slv', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('sq')); $expected = array( 'sq' => array('language' => 'Albanian', 'locale' => 'sqi', 'localeFallback' => 'sqi', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('sr')); $expected = array( 'sr' => array('language' => 'Serbian', 'locale' => 'srp', 'localeFallback' => 'srp', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('sv', 'sv-fi')); $expected = array( 'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), 'sv-fi' => array('language' => 'Swedish (Finland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('se')); $expected = array( 'se' => array('language' => 'Sami', 'locale' => 'sme', 'localeFallback' => 'sme', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('th')); $expected = array( 'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('tn')); $expected = array( 'tn' => array('language' => 'Tswana', 'locale' => 'tsn', 'localeFallback' => 'tsn', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('tr')); $expected = array( 'tr' => array('language' => 'Turkish', 'locale' => 'tur', 'localeFallback' => 'tur', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ts')); $expected = array( 'ts' => array('language' => 'Tsonga', 'locale' => 'tso', 'localeFallback' => 'tso', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('uk')); $expected = array( 'uk' => array('language' => 'Ukrainian', 'locale' => 'ukr', 'localeFallback' => 'ukr', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ur')); $expected = array( 'ur' => array('language' => 'Urdu', 'locale' => 'urd', 'localeFallback' => 'urd', 'charset' => 'utf-8', 'direction' => 'rtl') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('ve')); $expected = array( 've' => array('language' => 'Venda', 'locale' => 'ven', 'localeFallback' => 'ven', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('vi')); $expected = array( 'vi' => array('language' => 'Vietnamese', 'locale' => 'vie', 'localeFallback' => 'vie', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('cy')); $expected = array( 'cy' => array('language' => 'Welsh', 'locale' => 'cym', 'localeFallback' => 'cym', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('xh')); $expected = array( 'xh' => array('language' => 'Xhosa', 'locale' => 'xho', 'localeFallback' => 'xho', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('yi')); $expected = array( 'yi' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('zh', 'zh-cn', 'zh-hk', 'zh-sg', 'zh-tw')); $expected = array( 'zh' => array('language' => 'Chinese', 'locale' => 'zho', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), 'zh-cn' => array('language' => 'Chinese (PRC)', 'locale' => 'zh_cn', 'localeFallback' => 'zho', 'charset' => 'GB2312', 'direction' => 'ltr'), 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), 'zh-sg' => array('language' => 'Chinese (Singapore)', 'locale' => 'zh_sg', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), 'zh-tw' => array('language' => 'Chinese (Taiwan)', 'locale' => 'zh_tw', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('zu')); $expected = array( 'zu' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('en-nz', 'es-do', 'ar-lb', 'zh-hk', 'pt-br')); $expected = array( 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); $result = $localize->catalog(array('eng', 'deu', 'zho', 'rum', 'zul', 'yid')); $expected = array( 'eng' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'deu' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'zho' => array('language' => 'Chinese', 'locale' => 'zho', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), 'rum' => array('language' => 'Romanian', 'locale' => 'ron', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), 'zul' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8', 'direction' => 'ltr'), 'yid' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/I18n/I18nTest.php0000644000000000000000000032371413365153155021730 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.I18n * @since CakePHP(tm) v 1.2.0.5432 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('I18n', 'I18n'); App::uses('CakeSession', 'Model/Datasource'); /** * I18nTest class * * @package Cake.Test.Case.I18n */ class I18nTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); Cache::delete('object_map', '_cake_core_'); App::build(array( 'Locale' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Locale' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) ), App::RESET); CakePlugin::load(array('TestPlugin')); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); Cache::delete('object_map', '_cake_core_'); CakeSession::destroy(); App::build(); CakePlugin::unload(); } /** * testTranslationCaching method * * @return void */ public function testTranslationCaching() { Configure::write('Config.language', 'cache_test_po'); // reset internally stored entries I18n::clear(); Cache::clear(false, '_cake_core_'); $lang = Configure::read('Config.language'); Cache::config('_cake_core_', Cache::config('default')); // make some calls to translate using different domains $this->assertEquals('Dom 1 Foo', I18n::translate('dom1.foo', false, 'dom1')); $this->assertEquals('Dom 1 Bar', I18n::translate('dom1.bar', false, 'dom1')); $domains = I18n::domains(); $this->assertEquals('Dom 1 Foo', $domains['dom1']['cache_test_po']['LC_MESSAGES']['dom1.foo']['']); // reset internally stored entries I18n::clear(); // now only dom1 should be in cache $cachedDom1 = Cache::read('dom1_' . $lang, '_cake_core_'); $this->assertEquals('Dom 1 Foo', $cachedDom1['LC_MESSAGES']['dom1.foo']['']); $this->assertEquals('Dom 1 Bar', $cachedDom1['LC_MESSAGES']['dom1.bar']['']); // dom2 not in cache $this->assertFalse(Cache::read('dom2_' . $lang, '_cake_core_')); // translate a item of dom2 (adds dom2 to cache) $this->assertEquals('Dom 2 Foo', I18n::translate('dom2.foo', false, 'dom2')); // verify dom2 was cached through manual read from cache $cachedDom2 = Cache::read('dom2_' . $lang, '_cake_core_'); $this->assertEquals('Dom 2 Foo', $cachedDom2['LC_MESSAGES']['dom2.foo']['']); $this->assertEquals('Dom 2 Bar', $cachedDom2['LC_MESSAGES']['dom2.bar']['']); // modify cache entry manually to verify that dom1 entries now will be read from cache $cachedDom1['LC_MESSAGES']['dom1.foo'][''] = 'FOO'; Cache::write('dom1_' . $lang, $cachedDom1, '_cake_core_'); $this->assertEquals('FOO', I18n::translate('dom1.foo', false, 'dom1')); } /** * testDefaultStrings method * * @return void */ public function testDefaultStrings() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 1', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or > 1', $plurals)); $this->assertTrue(in_array('1 = 1', $plurals)); $this->assertTrue(in_array('2 = 0 or > 1', $plurals)); $this->assertTrue(in_array('3 = 0 or > 1', $plurals)); $this->assertTrue(in_array('4 = 0 or > 1', $plurals)); $this->assertTrue(in_array('5 = 0 or > 1', $plurals)); $this->assertTrue(in_array('6 = 0 or > 1', $plurals)); $this->assertTrue(in_array('7 = 0 or > 1', $plurals)); $this->assertTrue(in_array('8 = 0 or > 1', $plurals)); $this->assertTrue(in_array('9 = 0 or > 1', $plurals)); $this->assertTrue(in_array('10 = 0 or > 1', $plurals)); $this->assertTrue(in_array('11 = 0 or > 1', $plurals)); $this->assertTrue(in_array('12 = 0 or > 1', $plurals)); $this->assertTrue(in_array('13 = 0 or > 1', $plurals)); $this->assertTrue(in_array('14 = 0 or > 1', $plurals)); $this->assertTrue(in_array('15 = 0 or > 1', $plurals)); $this->assertTrue(in_array('16 = 0 or > 1', $plurals)); $this->assertTrue(in_array('17 = 0 or > 1', $plurals)); $this->assertTrue(in_array('18 = 0 or > 1', $plurals)); $this->assertTrue(in_array('19 = 0 or > 1', $plurals)); $this->assertTrue(in_array('20 = 0 or > 1', $plurals)); $this->assertTrue(in_array('21 = 0 or > 1', $plurals)); $this->assertTrue(in_array('22 = 0 or > 1', $plurals)); $this->assertTrue(in_array('23 = 0 or > 1', $plurals)); $this->assertTrue(in_array('24 = 0 or > 1', $plurals)); $this->assertTrue(in_array('25 = 0 or > 1', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 1 (from core)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core)', $corePlurals)); $this->assertTrue(in_array('2 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('3 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('4 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('5 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('6 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('7 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('8 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('9 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('10 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('11 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('12 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('13 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('14 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('15 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('16 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('17 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('18 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('19 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('20 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('21 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('22 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('23 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('24 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('25 = 0 or > 1 (from core)', $corePlurals)); } /** * testPoRulesZero method * * @return void */ public function testPoRulesZero() { Configure::write('Config.language', 'rule_0_po'); $this->assertRulesZero(); } /** * testMoRulesZero method * * @return void */ public function testMoRulesZero() { Configure::write('Config.language', 'rule_0_mo'); $this->assertRulesZero(); } /** * Assertions for rules zero. * * @return void */ public function assertRulesZero() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 0 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('1 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('2 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('3 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('4 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('5 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('6 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('7 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('8 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('9 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('10 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('11 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('12 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('13 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('14 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('15 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('16 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('17 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('18 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('19 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('20 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('21 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('22 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('23 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('24 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('25 ends with any # (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 0 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 ends with any # (from core translated)', $corePlurals)); } /** * testPoRulesOne method * * @return void */ public function testPoRulesOne() { Configure::write('Config.language', 'rule_1_po'); $this->assertRulesOne(); } /** * testMoRulesOne method * * @return void */ public function testMoRulesOne() { Configure::write('Config.language', 'rule_1_mo'); $this->assertRulesOne(); } /** * Assertions for plural rule one * * @return void */ public function assertRulesOne() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 1 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); $this->assertTrue(in_array('2 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('3 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('4 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('5 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('6 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('7 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('8 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('9 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('10 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('11 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('12 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('13 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('14 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('15 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('16 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('17 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('18 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('19 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('20 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('21 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('22 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('23 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('24 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('25 = 0 or > 1 (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 1 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 = 0 or > 1 (from core translated)', $corePlurals)); } /** * testMoRulesTwo method * * @return void */ public function testMoRulesTwo() { Configure::write('Config.language', 'rule_2_mo'); $this->assertRulesTwo(); } /** * testPoRulesTwo method * * @return void */ public function testPoRulesTwo() { Configure::write('Config.language', 'rule_2_po'); $this->assertRulesTwo(); } /** * Assertions for rules Two * * @return void */ public function assertRulesTwo() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 2 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or 1 (translated)', $plurals)); $this->assertTrue(in_array('1 = 0 or 1 (translated)', $plurals)); $this->assertTrue(in_array('2 > 1 (translated)', $plurals)); $this->assertTrue(in_array('3 > 1 (translated)', $plurals)); $this->assertTrue(in_array('4 > 1 (translated)', $plurals)); $this->assertTrue(in_array('5 > 1 (translated)', $plurals)); $this->assertTrue(in_array('6 > 1 (translated)', $plurals)); $this->assertTrue(in_array('7 > 1 (translated)', $plurals)); $this->assertTrue(in_array('8 > 1 (translated)', $plurals)); $this->assertTrue(in_array('9 > 1 (translated)', $plurals)); $this->assertTrue(in_array('10 > 1 (translated)', $plurals)); $this->assertTrue(in_array('11 > 1 (translated)', $plurals)); $this->assertTrue(in_array('12 > 1 (translated)', $plurals)); $this->assertTrue(in_array('13 > 1 (translated)', $plurals)); $this->assertTrue(in_array('14 > 1 (translated)', $plurals)); $this->assertTrue(in_array('15 > 1 (translated)', $plurals)); $this->assertTrue(in_array('16 > 1 (translated)', $plurals)); $this->assertTrue(in_array('17 > 1 (translated)', $plurals)); $this->assertTrue(in_array('18 > 1 (translated)', $plurals)); $this->assertTrue(in_array('19 > 1 (translated)', $plurals)); $this->assertTrue(in_array('20 > 1 (translated)', $plurals)); $this->assertTrue(in_array('21 > 1 (translated)', $plurals)); $this->assertTrue(in_array('22 > 1 (translated)', $plurals)); $this->assertTrue(in_array('23 > 1 (translated)', $plurals)); $this->assertTrue(in_array('24 > 1 (translated)', $plurals)); $this->assertTrue(in_array('25 > 1 (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 2 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 0 or 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 > 1 (from core translated)', $corePlurals)); } /** * testPoRulesThree method * * @return void */ public function testPoRulesThree() { Configure::write('Config.language', 'rule_3_po'); $this->assertRulesThree(); } /** * testMoRulesThree method * * @return void */ public function testMoRulesThree() { Configure::write('Config.language', 'rule_3_mo'); $this->assertRulesThree(); } /** * Assert rules for plural three. * * @return void */ public function assertRulesThree() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 3 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 (translated)', $plurals)); $this->assertTrue(in_array('1 ends 1 but not 11 (translated)', $plurals)); $this->assertTrue(in_array('2 everything else (translated)', $plurals)); $this->assertTrue(in_array('3 everything else (translated)', $plurals)); $this->assertTrue(in_array('4 everything else (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 everything else (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 ends 1 but not 11 (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 3 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends 1 but not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 ends 1 but not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesFour method * * @return void */ public function testPoRulesFour() { Configure::write('Config.language', 'rule_4_po'); $this->assertRulesFour(); } /** * testMoRulesFour method * * @return void */ public function testMoRulesFour() { Configure::write('Config.language', 'rule_4_mo'); $this->assertRulesFour(); } /** * Run the assertions for Rule 4 plurals. * * @return void */ public function assertRulesFour() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 4 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); $this->assertTrue(in_array('2 = 2 (translated)', $plurals)); $this->assertTrue(in_array('3 everything else (translated)', $plurals)); $this->assertTrue(in_array('4 everything else (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 everything else (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 4 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 = 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesFive method * * @return void */ public function testPoRulesFive() { Configure::write('Config.language', 'rule_5_po'); $this->assertRulesFive(); } /** * testMoRulesFive method * * @return void */ public function testMoRulesFive() { Configure::write('Config.language', 'rule_5_mo'); $this->assertRulesFive(); } /** * Run the assertions for rule 5 plurals * * @return void */ public function assertRulesFive() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 5 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); $this->assertTrue(in_array('2 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('3 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('4 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('5 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('6 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('7 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('8 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('9 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('10 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('11 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('12 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('13 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('14 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('15 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('16 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('17 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('18 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('19 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 5 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesSix method * * @return void */ public function testPoRulesSix() { Configure::write('Config.language', 'rule_6_po'); $this->assertRulesSix(); } /** * testMoRulesSix method * * @return void */ public function testMoRulesSix() { Configure::write('Config.language', 'rule_6_mo'); $this->assertRulesSix(); } /** * Assertions for the sixth plural rules. * * @return void */ public function assertRulesSix() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 6 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); $this->assertTrue(in_array('2 everything else (translated)', $plurals)); $this->assertTrue(in_array('3 everything else (translated)', $plurals)); $this->assertTrue(in_array('4 everything else (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('11 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('12 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('13 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('14 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('15 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('16 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('17 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('18 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('19 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('20 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('21 ends in 1, not 11 (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 6 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 ends in 1, not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesSeven method * * @return void */ public function testPoRulesSeven() { Configure::write('Config.language', 'rule_7_po'); $this->assertRulesSeven(); } /** * testMoRulesSeven method * * @return void */ public function testMoRulesSeven() { Configure::write('Config.language', 'rule_7_mo'); $this->assertRulesSeven(); } /** * Run assertions for seventh plural rules * * @return void */ public function assertRulesSeven() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 7 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 everything else (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 ends in 1, not 11 (translated)', $plurals)); $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 7 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 ends in 1, not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesEight method * * @return void */ public function testPoRulesEight() { Configure::write('Config.language', 'rule_8_po'); $this->assertRulesEight(); } /** * testMoRulesEight method * * @return void */ public function testMoRulesEight() { Configure::write('Config.language', 'rule_8_mo'); $this->assertRulesEight(); } /** * Run assertions for the eighth plural rule. * * @return void */ public function assertRulesEight() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 8 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 2-4 (translated)', $plurals)); $this->assertTrue(in_array('3 is 2-4 (translated)', $plurals)); $this->assertTrue(in_array('4 is 2-4 (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 everything else (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 8 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 2-4 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 is 2-4 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 is 2-4 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesNine method * * @return void */ public function testPoRulesNine() { Configure::write('Config.language', 'rule_9_po'); $this->assertRulesNine(); } /** * testMoRulesNine method * * @return void */ public function testMoRulesNine() { Configure::write('Config.language', 'rule_9_mo'); $this->assertRulesNine(); } /** * Assert plural rules nine * * @return void */ public function assertRulesNine() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 9 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 everything else (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 9 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesTen method * * @return void */ public function testPoRulesTen() { Configure::write('Config.language', 'rule_10_po'); $this->assertRulesTen(); } /** * testMoRulesTen method * * @return void */ public function testMoRulesTen() { Configure::write('Config.language', 'rule_10_mo'); $this->assertRulesTen(); } /** * Assertions for plural rules 10 * * @return void */ public function assertRulesTen() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 10 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); $this->assertTrue(in_array('2 ends in 2 (translated)', $plurals)); $this->assertTrue(in_array('3 ends in 03-04 (translated)', $plurals)); $this->assertTrue(in_array('4 ends in 03-04 (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 everything else (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 10 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends in 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 ends in 03-04 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 ends in 03-04 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesEleven method * * @return void */ public function testPoRulesEleven() { Configure::write('Config.language', 'rule_11_po'); $this->assertRulesEleven(); } /** * testMoRulesEleven method * * @return void */ public function testMoRulesEleven() { Configure::write('Config.language', 'rule_11_mo'); $this->assertRulesEleven(); } /** * Assertions for plural rules eleven * * @return void */ public function assertRulesEleven() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 11 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); $this->assertTrue(in_array('3 is 3-6 (translated)', $plurals)); $this->assertTrue(in_array('4 is 3-6 (translated)', $plurals)); $this->assertTrue(in_array('5 is 3-6 (translated)', $plurals)); $this->assertTrue(in_array('6 is 3-6 (translated)', $plurals)); $this->assertTrue(in_array('7 is 7-10 (translated)', $plurals)); $this->assertTrue(in_array('8 is 7-10 (translated)', $plurals)); $this->assertTrue(in_array('9 is 7-10 (translated)', $plurals)); $this->assertTrue(in_array('10 is 7-10 (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 11 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 is 3-6 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 is 3-6 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 is 3-6 (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 is 3-6 (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 is 7-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 is 7-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 is 7-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 is 7-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPoRulesTwelve method * * @return void */ public function testPoRulesTwelve() { Configure::write('Config.language', 'rule_12_po'); $this->assertRulesTwelve(); } /** * testMoRulesTwelve method * * @return void */ public function testMoRulesTwelve() { Configure::write('Config.language', 'rule_12_mo'); $this->assertRulesTwelve(); } /** * Assertions for plural rules twelve * * @return void */ public function assertRulesTwelve() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 12 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); $this->assertTrue(in_array('3 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('4 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('5 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('6 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('7 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('8 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('9 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('10 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('11 everything else (translated)', $plurals)); $this->assertTrue(in_array('12 everything else (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 12 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testMoRulesThirteen method * * @return void */ public function testmoRulesThirteen() { Configure::write('Config.language', 'rule_13_mo'); $this->assertRulesThirteen(); } /** * testPoRulesThirteen method * * @return void */ public function testPoRulesThirteen() { Configure::write('Config.language', 'rule_13_po'); $this->assertRulesThirteen(); } /** * Assertions for plural rules thirteen * * @return void */ public function assertRulesThirteen() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 13 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('3 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('4 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('5 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('6 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('7 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('8 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('9 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('10 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('11 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('12 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('13 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('14 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('15 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('16 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('17 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('18 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('19 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('20 ends in 11-20 (translated)', $plurals)); $this->assertTrue(in_array('21 everything else (translated)', $plurals)); $this->assertTrue(in_array('22 everything else (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 13 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 ends in 11-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testMoRulesFourteen method * * @return void */ public function testMoRulesFourteen() { Configure::write('Config.language', 'rule_14_mo'); $this->assertRulesFourteen(); } /** * testPoRulesFourteen method * * @return void */ public function testPoRulesFourteen() { Configure::write('Config.language', 'rule_14_po'); $this->assertRulesFourteen(); } /** * Assertions for plural rules fourteen * * @return void */ public function assertRulesFourteen() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 14 (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); $this->assertTrue(in_array('2 ends in 2 (translated)', $plurals)); $this->assertTrue(in_array('3 everything else (translated)', $plurals)); $this->assertTrue(in_array('4 everything else (translated)', $plurals)); $this->assertTrue(in_array('5 everything else (translated)', $plurals)); $this->assertTrue(in_array('6 everything else (translated)', $plurals)); $this->assertTrue(in_array('7 everything else (translated)', $plurals)); $this->assertTrue(in_array('8 everything else (translated)', $plurals)); $this->assertTrue(in_array('9 everything else (translated)', $plurals)); $this->assertTrue(in_array('10 everything else (translated)', $plurals)); $this->assertTrue(in_array('11 ends in 1 (translated)', $plurals)); $this->assertTrue(in_array('12 ends in 2 (translated)', $plurals)); $this->assertTrue(in_array('13 everything else (translated)', $plurals)); $this->assertTrue(in_array('14 everything else (translated)', $plurals)); $this->assertTrue(in_array('15 everything else (translated)', $plurals)); $this->assertTrue(in_array('16 everything else (translated)', $plurals)); $this->assertTrue(in_array('17 everything else (translated)', $plurals)); $this->assertTrue(in_array('18 everything else (translated)', $plurals)); $this->assertTrue(in_array('19 everything else (translated)', $plurals)); $this->assertTrue(in_array('20 everything else (translated)', $plurals)); $this->assertTrue(in_array('21 ends in 1 (translated)', $plurals)); $this->assertTrue(in_array('22 ends in 2 (translated)', $plurals)); $this->assertTrue(in_array('23 everything else (translated)', $plurals)); $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 14 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends in 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 ends in 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 ends in 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('21 ends in 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('22 ends in 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testMoRulesFifteen method * * @return void */ public function testMoRulesFifteen() { Configure::write('Config.language', 'rule_15_mo'); $this->assertRulesFifteen(); } /** * testPoRulesFifteen method * * @return void */ public function testPoRulesFifteen() { Configure::write('Config.language', 'rule_15_po'); $this->assertRulesFifteen(); } /** * Assertions for plural rules fifteen * * @return void */ public function assertRulesFifteen() { $singular = $this->_singular(); $this->assertEquals('Plural Rule 15 (translated)', $singular); $plurals = $this->_plural(111); $this->assertTrue(in_array('0 is 0 (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); $this->assertTrue(in_array('3 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('4 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('5 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('6 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('7 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('8 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('9 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('10 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('11 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('12 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('13 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('14 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('15 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('16 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('17 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('18 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('19 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('20 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('31 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('42 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('53 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('64 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('75 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('86 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('97 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('98 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('99 ends with 11-99 (translated)', $plurals)); $this->assertTrue(in_array('100 everything else (translated)', $plurals)); $this->assertTrue(in_array('101 everything else (translated)', $plurals)); $this->assertTrue(in_array('102 everything else (translated)', $plurals)); $this->assertTrue(in_array('103 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('104 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('105 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('106 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('107 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('108 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('109 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('110 ends with 03-10 (translated)', $plurals)); $this->assertTrue(in_array('111 ends with 11-99 (translated)', $plurals)); $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 15 (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(111); $this->assertTrue(in_array('0 is 0 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); $this->assertTrue(in_array('3 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('4 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('5 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('6 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('7 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('8 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('9 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('10 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('11 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('12 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('13 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('14 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('15 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('16 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('17 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('18 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('19 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('20 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('31 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('42 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('53 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('64 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('75 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('86 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('97 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('98 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('99 ends with 11-99 (from core translated)', $corePlurals)); $this->assertTrue(in_array('100 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('101 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('102 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('103 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('104 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('105 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('106 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('107 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('108 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('109 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('110 ends with 03-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('111 ends with 11-99 (from core translated)', $corePlurals)); } /** * testSetLanguageWithSession method * * @return void */ public function testSetLanguageWithSession() { CakeSession::write('Config.language', 'po'); $singular = $this->_singular(); $this->assertEquals('Po (translated)', $singular); $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (po translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (po translated)', $plurals)); $this->assertTrue(in_array('2 is 2-4 (po translated)', $plurals)); $this->assertTrue(in_array('3 is 2-4 (po translated)', $plurals)); $this->assertTrue(in_array('4 is 2-4 (po translated)', $plurals)); $this->assertTrue(in_array('5 everything else (po translated)', $plurals)); $this->assertTrue(in_array('6 everything else (po translated)', $plurals)); $this->assertTrue(in_array('7 everything else (po translated)', $plurals)); $this->assertTrue(in_array('8 everything else (po translated)', $plurals)); $this->assertTrue(in_array('9 everything else (po translated)', $plurals)); $this->assertTrue(in_array('10 everything else (po translated)', $plurals)); $this->assertTrue(in_array('11 everything else (po translated)', $plurals)); $this->assertTrue(in_array('12 everything else (po translated)', $plurals)); $this->assertTrue(in_array('13 everything else (po translated)', $plurals)); $this->assertTrue(in_array('14 everything else (po translated)', $plurals)); $this->assertTrue(in_array('15 everything else (po translated)', $plurals)); $this->assertTrue(in_array('16 everything else (po translated)', $plurals)); $this->assertTrue(in_array('17 everything else (po translated)', $plurals)); $this->assertTrue(in_array('18 everything else (po translated)', $plurals)); $this->assertTrue(in_array('19 everything else (po translated)', $plurals)); $this->assertTrue(in_array('20 everything else (po translated)', $plurals)); $this->assertTrue(in_array('21 everything else (po translated)', $plurals)); $this->assertTrue(in_array('22 everything else (po translated)', $plurals)); $this->assertTrue(in_array('23 everything else (po translated)', $plurals)); $this->assertTrue(in_array('24 everything else (po translated)', $plurals)); $this->assertTrue(in_array('25 everything else (po translated)', $plurals)); CakeSession::delete('Config.language'); } /** * testNoCoreTranslation method * * @return void */ public function testNoCoreTranslation() { Configure::write('Config.language', 'po'); $singular = $this->_singular(); $this->assertEquals('Po (translated)', $singular); $coreSingular = $this->_singularFromCore(); $this->assertNotEquals('Po (from core translated)', $coreSingular); $corePlurals = $this->_pluralFromCore(); $this->assertFalse(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertFalse(in_array('2 is 2-4 (from core translated)', $corePlurals)); $this->assertFalse(in_array('3 is 2-4 (from core translated)', $corePlurals)); $this->assertFalse(in_array('4 is 2-4 (from core translated)', $corePlurals)); $this->assertFalse(in_array('5 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('6 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('7 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('8 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('9 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('10 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('11 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('12 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('13 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('14 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('15 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('16 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('17 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('18 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('19 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('20 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('21 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('22 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('23 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('24 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('25 everything else (from core translated)', $corePlurals)); } /** * testPluginTranslation method * * @return void */ public function testPluginTranslation() { App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) )); Configure::write('Config.language', 'po'); $singular = $this->_domainSingular(); $this->assertEquals('Plural Rule 1 (from plugin)', $singular); $plurals = $this->_domainPlural(); $this->assertTrue(in_array('0 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('1 = 1 (from plugin)', $plurals)); $this->assertTrue(in_array('2 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('3 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('4 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('5 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('6 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('7 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('8 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('9 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('10 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('11 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('12 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('13 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('14 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('15 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('16 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('17 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('18 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('19 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('20 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('21 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('22 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('23 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('24 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('25 = 0 or > 1 (from plugin)', $plurals)); } /** * Test that Configure::read('I18n.preferApp') will prefer app. * * @return void */ public function testPluginTranslationPreferApp() { // Reset internally stored entries I18n::clear(); Cache::clear(false, '_cake_core_'); Configure::write('I18n.preferApp', true); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) )); Configure::write('Config.language', 'po'); $singular = $this->_domainSingular(); $this->assertEquals('Plural Rule 1', $singular); $plurals = $this->_domainPlural(); $this->assertTrue(in_array('0 = 0 or > 1', $plurals)); } /** * testPoMultipleLineTranslation method * * @return void */ public function testPoMultipleLineTranslation() { Configure::write('Config.language', 'po'); $string = "This is a multiline translation\n"; $string .= "broken up over multiple lines.\n"; $string .= "This is the third line.\n"; $string .= "This is the forth line."; $result = __($string); $expected = "This is a multiline translation\n"; $expected .= "broken up over multiple lines.\n"; $expected .= "This is the third line.\n"; $expected .= "This is the forth line. (translated)"; $this->assertEquals($expected, $result); // Windows Newline is \r\n $string = "This is a multiline translation\r\n"; $string .= "broken up over multiple lines.\r\n"; $string .= "This is the third line.\r\n"; $string .= "This is the forth line."; $result = __($string); $this->assertEquals($expected, $result); $singular = "valid\nsecond line"; $plural = "valids\nsecond line"; $result = __n($singular, $plural, 1); $expected = "v\nsecond line"; $this->assertEquals($expected, $result); $result = __n($singular, $plural, 2); $expected = "vs\nsecond line"; $this->assertEquals($expected, $result); $string = "This is a multiline translation\n"; $string .= "broken up over multiple lines.\n"; $string .= "This is the third line.\n"; $string .= "This is the forth line."; $singular = "%d = 1\n" . $string; $plural = "%d = 0 or > 1\n" . $string; $result = __n($singular, $plural, 1); $expected = "%d is 1\n" . $string; $this->assertEquals($expected, $result); $result = __n($singular, $plural, 2); $expected = "%d is 2-4\n" . $string; $this->assertEquals($expected, $result); // Windows Newline is \r\n $string = "This is a multiline translation\r\n"; $string .= "broken up over multiple lines.\r\n"; $string .= "This is the third line.\r\n"; $string .= "This is the forth line."; $singular = "%d = 1\r\n" . $string; $plural = "%d = 0 or > 1\r\n" . $string; $result = __n($singular, $plural, 1); $expected = "%d is 1\n" . str_replace("\r\n", "\n", $string); $this->assertEquals($expected, $result); $result = __n($singular, $plural, 2); $expected = "%d is 2-4\n" . str_replace("\r\n", "\n", $string); $this->assertEquals($expected, $result); } /** * testPoNoTranslationNeeded method * * @return void */ public function testPoNoTranslationNeeded() { Configure::write('Config.language', 'po'); $result = __('No Translation needed'); $this->assertEquals('No Translation needed', $result); } /** * testPoQuotedString method * * @return void */ public function testPoQuotedString() { Configure::write('Config.language', 'po'); $expected = 'this is a "quoted string" (translated)'; $this->assertEquals($expected, __('this is a "quoted string"')); } /** * testFloatValue method * * @return void */ public function testFloatValue() { Configure::write('Config.language', 'rule_9_po'); $result = __n('%d = 1', '%d = 0 or > 1', (float)1); $expected = '%d is 1 (translated)'; $this->assertEquals($expected, $result); $result = __n('%d = 1', '%d = 0 or > 1', (float)2); $expected = "%d ends in 2-4, not 12-14 (translated)"; $this->assertEquals($expected, $result); $result = __n('%d = 1', '%d = 0 or > 1', (float)5); $expected = "%d everything else (translated)"; $this->assertEquals($expected, $result); } /** * testCategory method * * @return void */ public function testCategory() { Configure::write('Config.language', 'po'); // Test with default (I18n constant) category. $category = $this->_category(); $this->assertEquals('Monetary Po (translated)', $category); // Test with category number represenation. $category = $this->_category(3); $this->assertEquals('Monetary Po (translated)', $category); } /** * testPluginCategory method * * @return void */ public function testPluginCategory() { Configure::write('Config.language', 'po'); $singular = $this->_domainCategorySingular(); $this->assertEquals('Monetary Plural Rule 1 (from plugin)', $singular); $plurals = $this->_domainCategoryPlural(); $this->assertTrue(in_array('Monetary 0 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('Monetary 1 = 1 (from plugin)', $plurals)); } /** * testCategoryThenSingular method * * @return void */ public function testCategoryThenSingular() { Configure::write('Config.language', 'po'); $category = $this->_category(); $this->assertEquals('Monetary Po (translated)', $category); $singular = $this->_singular(); $this->assertEquals('Po (translated)', $singular); } /** * testTimeDefinition method * * @return void */ public function testTimeDefinition() { Configure::write('Config.language', 'po'); $result = __c('d_fmt', 5); $expected = '%m/%d/%Y'; $this->assertEquals($expected, $result); $result = __c('am_pm', 5); $expected = array('AM', 'PM'); $this->assertEquals($expected, $result); $result = __c('abmon', 5); $expected = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); $this->assertEquals($expected, $result); } /** * testTimeDefinitionJapanese method * * @return void */ public function testTimeDefinitionJapanese() { Configure::write('Config.language', 'ja_jp'); $result = __c('d_fmt', 5); $expected = "%Yๅนด%mๆœˆ%dๆ—ฅ"; $this->assertEquals($expected, $result); $result = __c('am_pm', 5); $expected = array("ๅˆๅ‰", "ๅˆๅพŒ"); $this->assertEquals($expected, $result); $result = __c('abmon', 5); $expected = array(" 1ๆœˆ", " 2ๆœˆ", " 3ๆœˆ", " 4ๆœˆ", " 5ๆœˆ", " 6ๆœˆ", " 7ๆœˆ", " 8ๆœˆ", " 9ๆœˆ", "10ๆœˆ", "11ๆœˆ", "12ๆœˆ"); $this->assertEquals($expected, $result); } /** * testTranslateLanguageParam method * * @return void */ public function testTranslateLanguageParam() { Configure::write('Config.language', 'rule_0_po'); $result = I18n::translate('Plural Rule 1', null, null, I18n::LC_MESSAGES); $expected = 'Plural Rule 0 (translated)'; $this->assertEquals($expected, $result); $result = I18n::translate('Plural Rule 1', null, null, I18n::LC_MESSAGES, null, 'rule_1_po'); $expected = 'Plural Rule 1 (translated)'; $this->assertEquals($expected, $result); } /** * Test that the '' domain causes exceptions. * * @expectedException CakeException * @return void */ public function testTranslateEmptyDomain() { I18n::translate('Plural Rule 1', null, ''); } /** * testLoadLocaleDefinition method * * @return void */ public function testLoadLocaleDefinition() { $path = current(App::path('locales')); $result = I18n::loadLocaleDefinition($path . 'nld' . DS . 'LC_TIME'); $expected = array('zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'); $this->assertSame($expected, $result['day']); } /** * Test basic context support * * @return void */ public function testContext() { Configure::write('Config.language', 'nld'); $this->assertSame("brief", __x('mail', 'letter')); $this->assertSame("letter", __x('character', 'letter')); $this->assertSame("bal", __x('spherical object', 'ball')); $this->assertSame("danspartij", __x('social gathering', 'ball')); $this->assertSame("balans", __('balance')); $this->assertSame("saldo", __x('money', 'balance')); } /** * Test basic context support using mo files. * * @return void */ public function testContextMoFile() { Configure::write('Config.language', 'nld_mo'); $this->assertSame("brief", __x('mail', 'letter')); $this->assertSame("letter", __x('character', 'letter')); $this->assertSame("bal", __x('spherical object', 'ball')); $this->assertSame("danspartij", __x('social gathering', 'ball')); $this->assertSame("balans", __('balance')); $this->assertSame("saldo", __x('money', 'balance')); // MO file is sorted by msgid, 'zoo' should be last $this->assertSame("dierentuin", __('zoo')); } /** * Singular method * * @return void */ protected function _domainCategorySingular($domain = 'test_plugin', $category = 3) { $singular = __dc($domain, 'Plural Rule 1', $category); return $singular; } /** * Plural method * * @return void */ protected function _domainCategoryPlural($domain = 'test_plugin', $category = 3) { $plurals = array(); for ($number = 0; $number <= 25; $number++) { $plurals[] = sprintf(__dcn($domain, '%d = 1', '%d = 0 or > 1', (float)$number, $category), (float)$number); } return $plurals; } /** * Singular method * * @return void */ protected function _domainSingular($domain = 'test_plugin') { $singular = __d($domain, 'Plural Rule 1'); return $singular; } /** * Plural method * * @return void */ protected function _domainPlural($domain = 'test_plugin') { $plurals = array(); for ($number = 0; $number <= 25; $number++) { $plurals[] = sprintf(__dn($domain, '%d = 1', '%d = 0 or > 1', (float)$number), (float)$number); } return $plurals; } /** * category method * * @return void */ protected function _category($category = I18n::LC_MONETARY) { $singular = __c('Plural Rule 1', $category); return $singular; } /** * Singular method * * @return void */ protected function _singular() { $singular = __('Plural Rule 1'); return $singular; } /** * Plural method * * @param int $upTo For numbers upto (default to 25) * @return void */ protected function _plural($upTo = 25) { $plurals = array(); for ($number = 0; $number <= $upTo; $number++) { $plurals[] = sprintf(__n('%d = 1', '%d = 0 or > 1', (float)$number), (float)$number); } return $plurals; } /** * singularFromCore method * * @return void */ protected function _singularFromCore() { $singular = __('Plural Rule 1 (from core)'); return $singular; } /** * pluralFromCore method * * @param int $upTo For numbers upto (default to 25) * @return void */ protected function _pluralFromCore($upTo = 25) { $plurals = array(); for ($number = 0; $number <= $upTo; $number++) { $plurals[] = sprintf(__n('%d = 1 (from core)', '%d = 0 or > 1 (from core)', (float)$number), (float)$number); } return $plurals; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllErrorTest.php0000644000000000000000000000213213365153155022200 0ustar rootrootaddTestDirectory($libs . 'Error'); return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/AllI18nTest.php0000644000000000000000000000212213365153155021625 0ustar rootrootaddTestDirectory(CORE_TEST_CASES . DS . 'I18n'); return $suite; } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/View/0000755000000000000000000000000013365153155020021 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/View/ScaffoldViewTest.php0000644000000000000000000003453513365153155023760 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.View * @since CakePHP(tm) v 2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Controller', 'Controller'); App::uses('Scaffold', 'Controller'); App::uses('ScaffoldView', 'View'); App::uses('AppModel', 'Model'); require_once dirname(dirname(__FILE__)) . DS . 'Model' . DS . 'models.php'; /** * TestScaffoldView class * * @package Cake.Test.Case.Controller */ class TestScaffoldView extends ScaffoldView { /** * testGetFilename method * * @param string $action * @return void */ public function testGetFilename($action) { return $this->_getViewFileName($action); } } /** * ScaffoldViewMockController class * * @package Cake.Test.Case.Controller */ class ScaffoldViewMockController extends Controller { /** * name property * * @var string */ public $name = 'ScaffoldMock'; /** * scaffold property * * @var mixed */ public $scaffold; } /** * ScaffoldViewTest class * * @package Cake.Test.Case.Controller */ class ScaffoldViewTest extends CakeTestCase { /** * fixtures property * * @var array */ public $fixtures = array('core.article', 'core.user', 'core.comment', 'core.join_thing', 'core.tag'); /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->request = new CakeRequest(null, false); $this->Controller = new ScaffoldViewMockController($this->request); $this->Controller->response = $this->getMock('CakeResponse', array('_sendHeader')); App::build(array( 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS), 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) )); CakePlugin::load('TestPlugin'); } /** * tearDown method * * @return void */ public function tearDown() { unset($this->Controller, $this->request); parent::tearDown(); } /** * testGetViewFilename method * * @return void */ public function testGetViewFilename() { $_admin = Configure::read('Routing.prefixes'); Configure::write('Routing.prefixes', array('admin')); $this->Controller->request->params['action'] = 'index'; $ScaffoldView = new TestScaffoldView($this->Controller); $result = $ScaffoldView->testGetFilename('index'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'index.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('edit'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'form.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('add'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'form.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('view'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'view.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('admin_index'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'index.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('admin_view'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'view.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('admin_edit'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'form.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('admin_add'); $expected = CAKE . 'View' . DS . 'Scaffolds' . DS . 'form.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('error'); $expected = CAKE . 'View' . DS . 'Errors' . DS . 'scaffold_error.ctp'; $this->assertEquals($expected, $result); $Controller = new ScaffoldViewMockController($this->request); $Controller->scaffold = 'admin'; $Controller->viewPath = 'Posts'; $Controller->request['action'] = 'admin_edit'; $ScaffoldView = new TestScaffoldView($Controller); $result = $ScaffoldView->testGetFilename('admin_edit'); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Posts' . DS . 'scaffold.form.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('edit'); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Posts' . DS . 'scaffold.form.ctp'; $this->assertEquals($expected, $result); $Controller = new ScaffoldViewMockController($this->request); $Controller->scaffold = 'admin'; $Controller->viewPath = 'Tests'; $Controller->request->addParams(array( 'plugin' => 'test_plugin', 'action' => 'admin_add', 'admin' => true )); $Controller->plugin = 'TestPlugin'; $ScaffoldView = new TestScaffoldView($Controller); $result = $ScaffoldView->testGetFilename('admin_add'); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS . 'TestPlugin' . DS . 'View' . DS . 'Tests' . DS . 'scaffold.form.ctp'; $this->assertEquals($expected, $result); $result = $ScaffoldView->testGetFilename('add'); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS . 'TestPlugin' . DS . 'View' . DS . 'Tests' . DS . 'scaffold.form.ctp'; $this->assertEquals($expected, $result); Configure::write('Routing.prefixes', $_admin); } /** * test getting the view file name for themed scaffolds. * * @return void */ public function testGetViewFileNameWithTheme() { $this->Controller->request['action'] = 'index'; $this->Controller->viewPath = 'Posts'; $this->Controller->theme = 'TestTheme'; $ScaffoldView = new TestScaffoldView($this->Controller); $result = $ScaffoldView->testGetFilename('index'); $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS . 'Posts' . DS . 'scaffold.index.ctp'; $this->assertEquals($expected, $result); } /** * test default index scaffold generation * * @return void */ public function testIndexScaffold() { $params = array( 'plugin' => null, 'pass' => array(), 'form' => array(), 'named' => array(), 'url' => array('url' => 'scaffold_mock'), 'controller' => 'scaffold_mock', 'action' => 'index', ); $this->Controller->request->addParams($params); $this->Controller->request->webroot = '/'; $this->Controller->request->base = ''; $this->Controller->request->here = '/scaffold_mock/index'; //set router. Router::reload(); Router::setRequestInfo($this->Controller->request); $this->Controller->constructClasses(); ob_start(); new Scaffold($this->Controller, $this->Controller->request); $this->Controller->response->send(); $result = ob_get_clean(); $this->assertRegExp('#

Scaffold Mock

#', $result); $this->assertRegExp('##', $result); $this->assertRegExp('#1#', $result); //belongsTo links $this->assertRegExp('#
  • New Scaffold Mock
  • #', $result); $this->assertRegExp('#
  • List Scaffold Users
  • #', $result); $this->assertRegExp('#
  • New Comment
  • #', $result); } /** * test default view scaffold generation * * @return void */ public function testViewScaffold() { $this->Controller->request->base = ''; $this->Controller->request->here = '/scaffold_mock'; $this->Controller->request->webroot = '/'; $params = array( 'plugin' => null, 'pass' => array(1), 'form' => array(), 'named' => array(), 'url' => array('url' => 'scaffold_mock/view/1'), 'controller' => 'scaffold_mock', 'action' => 'view', ); $this->Controller->request->addParams($params); //set router. Router::reload(); Router::setRequestInfo($this->Controller->request); $this->Controller->constructClasses(); ob_start(); new Scaffold($this->Controller, $this->Controller->request); $this->Controller->response->send(); $result = ob_get_clean(); $this->assertRegExp('/

    View Scaffold Mock<\/h2>/', $result); $this->assertRegExp('/
    /', $result); $this->assertRegExp('/1<\/a>/', $result); //belongsTo links $this->assertRegExp('/
  • Edit Scaffold Mock<\/a>\s<\/li>/', $result); $this->assertRegExp('/\s*

    Related Scaffold Comments<\/h3>\s*

  • /', $result); $this->assertRegExp('/
  • New Comment<\/a><\/li>/', $result); $this->assertNotRegExp('/
  • JoinThing<\/th>/', $result); } /** * test default view scaffold generation * * @return void */ public function testEditScaffold() { $this->Controller->request->base = ''; $this->Controller->request->webroot = '/'; $this->Controller->request->here = '/scaffold_mock/edit/1'; $params = array( 'plugin' => null, 'pass' => array(1), 'form' => array(), 'named' => array(), 'url' => array('url' => 'scaffold_mock'), 'controller' => 'scaffold_mock', 'action' => 'edit', ); $this->Controller->request->addParams($params); //set router. Router::reload(); Router::setRequestInfo($this->Controller->request); $this->Controller->constructClasses(); ob_start(); new Scaffold($this->Controller, $this->Controller->request); $this->Controller->response->send(); $result = ob_get_clean(); $this->assertContains('
    assertContains('Edit Scaffold Mock', $result); $this->assertContains('input type="hidden" name="data[ScaffoldMock][id]" value="1" id="ScaffoldMockId"', $result); $this->assertContains('select name="data[ScaffoldMock][user_id]" id="ScaffoldMockUserId"', $result); $this->assertContains('input name="data[ScaffoldMock][title]" maxlength="255" type="text" value="First Article" id="ScaffoldMockTitle"', $result); $this->assertContains('input name="data[ScaffoldMock][published]" maxlength="1" type="text" value="Y" id="ScaffoldMockPublished"', $result); $this->assertContains('textarea name="data[ScaffoldMock][body]" cols="30" rows="6" id="ScaffoldMockBody"', $result); $this->assertRegExp('//', $result); $this->assertRegExp('/
  • New Scaffold Mock<\/a><\/li>/', $result); Configure::write('Routing.prefixes', $_backAdmin); } /** * Test Admin Index Scaffolding. * * @return void */ public function testAdminEditScaffold() { Configure::write('Routing.prefixes', array('admin')); $params = array( 'plugin' => null, 'pass' => array(1), 'form' => array(), 'named' => array(), 'prefix' => 'admin', 'url' => array('url' => 'admin/scaffold_mock/edit/1'), 'controller' => 'scaffold_mock', 'action' => 'admin_edit', 'admin' => 1, ); $this->Controller->request->base = ''; $this->Controller->request->webroot = '/'; $this->Controller->request->here = '/admin/scaffold_mock/edit/1'; $this->Controller->request->addParams($params); //reset, and set router. Router::reload(); Router::setRequestInfo($this->Controller->request); $this->Controller->scaffold = 'admin'; $this->Controller->constructClasses(); ob_start(); new Scaffold($this->Controller, $this->Controller->request); $this->Controller->response->send(); $result = ob_get_clean(); $this->assertRegExp('#admin/scaffold_mock/edit/1#', $result); $this->assertRegExp('#Scaffold Mock#', $result); } /** * Test Admin Index Scaffolding. * * @return void */ public function testMultiplePrefixScaffold() { $_backAdmin = Configure::read('Routing.prefixes'); Configure::write('Routing.prefixes', array('admin', 'member')); $params = array( 'plugin' => null, 'pass' => array(), 'form' => array(), 'named' => array(), 'prefix' => 'member', 'url' => array('url' => 'member/scaffold_mock'), 'controller' => 'scaffold_mock', 'action' => 'member_index', 'member' => 1, ); $this->Controller->request->base = ''; $this->Controller->request->webroot = '/'; $this->Controller->request->here = '/member/scaffold_mock'; $this->Controller->request->addParams($params); //reset, and set router. Router::reload(); Router::setRequestInfo($this->Controller->request); $this->Controller->scaffold = 'member'; $this->Controller->constructClasses(); ob_start(); new Scaffold($this->Controller, $this->Controller->request); $this->Controller->response->send(); $result = ob_get_clean(); $this->assertRegExp('/

    Scaffold Mock<\/h2>/', $result); $this->assertRegExp('//', $result); $this->assertRegExp('/
  • New Scaffold Mock<\/a><\/li>/', $result); Configure::write('Routing.prefixes', $_backAdmin); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/View/Helper/0000755000000000000000000000000013365153155021240 5ustar rootrootZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php0000644000000000000000000031300413365153155025676 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.View.Helper * @since CakePHP(tm) v 1.2.0.4206 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('View', 'View'); App::uses('HtmlHelper', 'View/Helper'); App::uses('JsHelper', 'View/Helper'); App::uses('PaginatorHelper', 'View/Helper'); App::uses('FormHelper', 'View/Helper'); if (!defined('FULL_BASE_URL')) { define('FULL_BASE_URL', 'https://cakephp.org'); } /** * PaginatorHelperTest class * * @package Cake.Test.Case.View.Helper */ class PaginatorHelperTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); Configure::write('Config.language', 'eng'); $controller = null; $this->View = new View($controller); $this->Paginator = new PaginatorHelper($this->View); $this->Paginator->Js = $this->getMock('PaginatorHelper', array(), array($this->View)); $this->Paginator->request = new CakeRequest(null, false); $this->Paginator->request->addParams(array( 'paging' => array( 'Article' => array( 'page' => 2, 'current' => 9, 'count' => 62, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 7, 'order' => null, 'limit' => 20, 'options' => array( 'page' => 1, 'conditions' => array() ), 'paramType' => 'named' ) ) )); $this->Paginator->Html = new HtmlHelper($this->View); Configure::write('Routing.prefixes', array()); Router::reload(); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); unset($this->View, $this->Paginator); } /** * testHasPrevious method * * @return void */ public function testHasPrevious() { $this->assertFalse($this->Paginator->hasPrev()); $this->Paginator->request->params['paging']['Article']['prevPage'] = true; $this->assertTrue($this->Paginator->hasPrev()); $this->Paginator->request->params['paging']['Article']['prevPage'] = false; } /** * testHasNext method * * @return void */ public function testHasNext() { $this->assertTrue($this->Paginator->hasNext()); $this->Paginator->request->params['paging']['Article']['nextPage'] = false; $this->assertFalse($this->Paginator->hasNext()); $this->Paginator->request->params['paging']['Article']['nextPage'] = true; } /** * testDisabledLink method * * @return void */ public function testDisabledLink() { $this->Paginator->request->params['paging']['Article']['nextPage'] = false; $this->Paginator->request->params['paging']['Article']['page'] = 1; $result = $this->Paginator->next('Next', array(), true); $expected = 'Next'; $this->assertEquals($expected, $result); $this->Paginator->request->params['paging']['Article']['prevPage'] = false; $result = $this->Paginator->prev('prev', array('update' => 'theList', 'indicator' => 'loading', 'url' => array('controller' => 'posts')), null, array('class' => 'disabled', 'tag' => 'span')); $expected = array( 'span' => array('class' => 'disabled'), 'prev', '/span' ); $this->assertTags($result, $expected); } /** * testSortLinks method * * @return void */ public function testSortLinks() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/')), array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/') )); $this->Paginator->options(array('url' => array('param'))); $this->Paginator->request['paging'] = array( 'Article' => array( 'current' => 9, 'count' => 62, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 7, 'options' => array( 'page' => 1, 'order' => array('date' => 'asc'), 'conditions' => array() ), 'paramType' => 'named' ) ); $result = $this->Paginator->sort('title'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:asc'), 'Title', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->sort('date'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/sort:date/direction:desc', 'class' => 'asc'), 'Date', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->sort('title', 'TestTitle'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:asc'), 'TestTitle', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->sort('title', array('asc' => 'ascending', 'desc' => 'descending')); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:asc'), 'ascending', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['sort'] = 'title'; $result = $this->Paginator->sort('title', array('asc' => 'ascending', 'desc' => 'descending')); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:desc', 'class' => 'asc'), 'descending', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title'); $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title'); $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'desc')); $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'ASC')); $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'asc')); $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'desc')); $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'desc', 'class' => 'foo')); $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="foo asc">Title<\/a>$/', $result); } /** * testSortLinksWithLockOption method * * @return void */ public function testSortLinksWithLockOption() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/')), array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/') )); $this->Paginator->options(array('url' => array('param'))); $this->Paginator->request['paging'] = array( 'Article' => array( 'current' => 9, 'count' => 62, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 7, 'options' => array( 'page' => 1, 'order' => array('date' => 'asc'), 'conditions' => array() ), 'paramType' => 'named' ) ); $result = $this->Paginator->sort('distance', null, array('lock' => true)); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/sort:distance/direction:asc'), 'Distance', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['sort'] = 'distance'; $result = $this->Paginator->sort('distance', null, array('lock' => true)); $expected = array( 'a' => array('href' => '/officespace/accounts/index/param/sort:distance/direction:asc', 'class' => 'asc locked'), 'Distance', '/a' ); $this->assertTags($result, $expected); } /** * test that sort() works with virtual field order options. * * @return void */ public function testSortLinkWithVirtualField() { Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'form' => array(), 'url' => array('url' => 'accounts/')), array('base' => '', 'here' => '/accounts/', 'webroot' => '/') )); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('full_name' => 'asc'); $result = $this->Paginator->sort('Article.full_name'); $expected = array( 'a' => array('href' => '/accounts/index/sort:Article.full_name/direction:desc', 'class' => 'asc'), 'Article Full Name', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->sort('full_name'); $expected = array( 'a' => array('href' => '/accounts/index/sort:full_name/direction:desc', 'class' => 'asc'), 'Full Name', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('full_name' => 'desc'); $result = $this->Paginator->sort('Article.full_name'); $expected = array( 'a' => array('href' => '/accounts/index/sort:Article.full_name/direction:asc', 'class' => 'desc'), 'Article Full Name', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->sort('full_name'); $expected = array( 'a' => array('href' => '/accounts/index/sort:full_name/direction:asc', 'class' => 'desc'), 'Full Name', '/a' ); $this->assertTags($result, $expected); } /** * testSortLinksUsingDirectionOption method * * @return void */ public function testSortLinksUsingDirectionOption() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/', 'mod_rewrite' => 'true')), array('base' => '/', 'here' => '/accounts/', 'webroot' => '/') )); $this->Paginator->options(array('url' => array('param'))); $result = $this->Paginator->sort('title', 'TestTitle', array('direction' => 'desc')); $expected = array( 'a' => array('href' => '/accounts/index/param/sort:title/direction:desc'), 'TestTitle', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->sort('title', array('asc' => 'ascending', 'desc' => 'descending'), array('direction' => 'desc')); $expected = array( 'a' => array('href' => '/accounts/index/param/sort:title/direction:desc'), 'descending', '/a' ); $this->assertTags($result, $expected); } /** * testSortLinksUsingDotNotation method * * @return void */ public function testSortLinksUsingDotNotation() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'form' => array(), 'url' => array('url' => 'accounts/', 'mod_rewrite' => 'true'), 'bare' => 0), array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/') )); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $result = $this->Paginator->sort('Article.title'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/sort:Article.title/direction:asc', 'class' => 'desc'), 'Article Title', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $result = $this->Paginator->sort('Article.title', 'Title'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/sort:Article.title/direction:asc', 'class' => 'desc'), 'Title', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $result = $this->Paginator->sort('Article.title', 'Title'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/sort:Article.title/direction:desc', 'class' => 'asc'), 'Title', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Account.title' => 'asc'); $result = $this->Paginator->sort('title'); $expected = array( 'a' => array('href' => '/officespace/accounts/index/sort:title/direction:asc'), 'Title', '/a' ); $this->assertTags($result, $expected); } /** * test multiple pagination sort links * * @return void */ public function testSortLinksMultiplePagination() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array( 'plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'form' => array(), 'url' => array('url' => 'accounts/', 'mod_rewrite' => 'true'), 'bare' => 0 ), array('base' => '', 'here' => '/accounts/', 'webroot' => '/') )); $this->Paginator->options(array('model' => 'Articles')); $this->Paginator->request['paging'] = array( 'Articles' => array( 'current' => 9, 'count' => 62, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 7, 'order' => null, 'options' => array( 'page' => 1, ), 'paramType' => 'named', 'queryScope' => 'article' ) ); $result = $this->Paginator->sort('title'); $expected = array( 'a' => array('href' => '/accounts/index/article%5Bsort%5D:title/article%5Bdirection%5D:asc/article%5Border%5D:', 'model' => 'Articles'), 'Title', '/a' ); $this->assertTags($result, $expected); } /** * testSortKey method * * @return void */ public function testSortKey() { $result = $this->Paginator->sortKey(null, array( 'order' => array('Article.title' => 'desc' ))); $this->assertEquals('Article.title', $result); $result = $this->Paginator->sortKey('Article', array('order' => 'Article.title')); $this->assertEquals('Article.title', $result); $result = $this->Paginator->sortKey('Article', array('sort' => 'Article.title')); $this->assertEquals('Article.title', $result); $result = $this->Paginator->sortKey('Article', array('sort' => 'Article')); $this->assertEquals('Article', $result); } /** * Test that sortKey falls back to the default sorting options set * in the $params which are the default pagination options. * * @return void */ public function testSortKeyFallbackToParams() { $this->Paginator->request->params['paging']['Article']['order'] = 'Article.body'; $result = $this->Paginator->sortKey(); $this->assertEquals('Article.body', $result); $result = $this->Paginator->sortKey('Article'); $this->assertEquals('Article.body', $result); $this->Paginator->request->params['paging']['Article']['order'] = array( 'Article.body' => 'DESC' ); $result = $this->Paginator->sortKey(); $this->assertEquals('Article.body', $result); $result = $this->Paginator->sortKey('Article'); $this->assertEquals('Article.body', $result); } /** * testSortDir method * * @return void */ public function testSortDir() { $result = $this->Paginator->sortDir(); $expected = 'asc'; $this->assertEquals($expected, $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $result = $this->Paginator->sortDir(); $expected = 'desc'; $this->assertEquals($expected, $result); unset($this->Paginator->request->params['paging']['Article']['options']); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $result = $this->Paginator->sortDir(); $expected = 'asc'; $this->assertEquals($expected, $result); unset($this->Paginator->request->params['paging']['Article']['options']); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('title' => 'desc'); $result = $this->Paginator->sortDir(); $expected = 'desc'; $this->assertEquals($expected, $result); unset($this->Paginator->request->params['paging']['Article']['options']); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('title' => 'asc'); $result = $this->Paginator->sortDir(); $expected = 'asc'; $this->assertEquals($expected, $result); unset($this->Paginator->request->params['paging']['Article']['options']); $this->Paginator->request->params['paging']['Article']['options']['direction'] = 'asc'; $result = $this->Paginator->sortDir(); $expected = 'asc'; $this->assertEquals($expected, $result); unset($this->Paginator->request->params['paging']['Article']['options']); $this->Paginator->request->params['paging']['Article']['options']['direction'] = 'desc'; $result = $this->Paginator->sortDir(); $expected = 'desc'; $this->assertEquals($expected, $result); unset($this->Paginator->request->params['paging']['Article']['options']); $result = $this->Paginator->sortDir('Article', array('direction' => 'asc')); $expected = 'asc'; $this->assertEquals($expected, $result); $result = $this->Paginator->sortDir('Article', array('direction' => 'desc')); $expected = 'desc'; $this->assertEquals($expected, $result); $result = $this->Paginator->sortDir('Article', array('direction' => 'asc')); $expected = 'asc'; $this->assertEquals($expected, $result); } /** * Test that sortDir falls back to the default sorting options set * in the $params which are the default pagination options. * * @return void */ public function testSortDirFallbackToParams() { $this->Paginator->request->params['paging']['Article']['order'] = array( 'Article.body' => 'ASC' ); $result = $this->Paginator->sortDir(); $this->assertEquals('asc', $result); $result = $this->Paginator->sortDir('Article'); $this->assertEquals('asc', $result); $this->Paginator->request->params['paging']['Article']['order'] = array( 'Article.body' => 'DESC' ); $result = $this->Paginator->sortDir(); $this->assertEquals('desc', $result); $result = $this->Paginator->sortDir('Article'); $this->assertEquals('desc', $result); } /** * testSortAdminLinks method * * @return void */ public function testSortAdminLinks() { Configure::write('Routing.prefixes', array('admin')); Router::reload(); Router::setRequestInfo(array( array('pass' => array(), 'named' => array(), 'controller' => 'users', 'plugin' => null, 'action' => 'admin_index', 'prefix' => 'admin', 'admin' => true, 'url' => array('ext' => 'html', 'url' => 'admin/users')), array('base' => '', 'here' => '/admin/users', 'webroot' => '/') )); Router::parse('/admin/users'); $this->Paginator->request->params['paging']['Article']['page'] = 1; $result = $this->Paginator->next('Next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/admin/users/index/page:2', 'rel' => 'next'), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); Router::reload(); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'test', 'action' => 'admin_index', 'pass' => array(), 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/test')), array('base' => '', 'here' => '/admin/test', 'webroot' => '/') )); Router::parse('/'); $this->Paginator->options(array('url' => array('param'))); $result = $this->Paginator->sort('title'); $expected = array( 'a' => array('href' => '/admin/test/index/param/sort:title/direction:asc'), 'Title', '/a' ); $this->assertTags($result, $expected); $this->Paginator->options(array('url' => array('param'))); $result = $this->Paginator->sort('Article.title', 'Title'); $expected = array( 'a' => array('href' => '/admin/test/index/param/sort:Article.title/direction:asc'), 'Title', '/a' ); $this->assertTags($result, $expected); } /** * testUrlGeneration method * * @return void */ public function testUrlGeneration() { $result = $this->Paginator->sort('controller'); $expected = array( 'a' => array('href' => '/index/sort:controller/direction:asc'), 'Controller', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->url(); $this->assertEquals('/', $result); $this->Paginator->request->params['paging']['Article']['options']['page'] = 2; $result = $this->Paginator->url(); $this->assertEquals('/index/page:2', $result); $options = array('order' => array('Article' => 'desc')); $result = $this->Paginator->url($options); $this->assertEquals('/index/page:2/sort:Article/direction:desc', $result); $this->Paginator->request->params['paging']['Article']['options']['page'] = 3; $options = array('order' => array('Article.name' => 'desc')); $result = $this->Paginator->url($options); $this->assertEquals('/index/page:3/sort:Article.name/direction:desc', $result); } /** * test URL generation with prefix routes * * @return void */ public function testUrlGenerationWithPrefixes() { Configure::write('Routing.prefixes', array('members')); Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('controller' => 'posts', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), array('base' => '', 'here' => 'posts/index', 'webroot' => '/') )); $this->Paginator->request->params['paging']['Article']['options']['page'] = 2; $this->Paginator->request->params['paging']['Article']['page'] = 2; $this->Paginator->request->params['paging']['Article']['prevPage'] = true; $options = array('members' => true); $result = $this->Paginator->url($options); $expected = '/members/posts/index/page:2'; $this->assertEquals($expected, $result); $result = $this->Paginator->sort('name', null, array('url' => $options)); $expected = array( 'a' => array('href' => '/members/posts/index/page:2/sort:name/direction:asc'), 'Name', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('next', array('url' => $options)); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/members/posts/index/page:3', 'rel' => 'next'), 'next', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('prev', array('url' => $options)); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/members/posts', 'rel' => 'prev'), 'prev', '/a', '/span' ); $this->assertTags($result, $expected); $options = array('members' => true, 'controller' => 'posts', 'order' => array('name' => 'desc')); $result = $this->Paginator->url($options); $expected = '/members/posts/index/page:2/sort:name/direction:desc'; $this->assertEquals($expected, $result); $options = array('controller' => 'posts', 'order' => array('Article.name' => 'desc')); $result = $this->Paginator->url($options); $expected = '/posts/index/page:2/sort:Article.name/direction:desc'; $this->assertEquals($expected, $result); } /** * test url with multiple pagination * * @return void */ public function testUrlMultiplePagination() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('controller' => 'posts', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), array('base' => '', 'here' => 'posts/index', 'webroot' => '/') )); $this->Paginator->request->params['paging']['Article']['queryScope'] = 'article'; $this->Paginator->request->params['paging']['Article']['page'] = 3; $this->Paginator->request->params['paging']['Article']['options']['page'] = 3; $this->Paginator->request->params['paging']['Article']['prevPage'] = true; $this->Paginator->options(array('model' => 'Article')); $result = $this->Paginator->url(array()); $expected = '/posts/index/article%5Bpage%5D:3'; $this->assertEquals($expected, $result); $result = $this->Paginator->sort('name'); $expected = array( 'a' => array( 'href' => '/posts/index/article%5Bpage%5D:3/article%5Bsort%5D:name/article%5Bdirection%5D:asc/article%5Border%5D:', 'model' => 'Article' ), 'Name', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/posts/index/article%5Bpage%5D:4', 'rel' => 'next', 'model' => 'Article'), 'next', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('prev'); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/posts/index/article%5Bpage%5D:2', 'rel' => 'prev', 'model' => 'Article'), 'prev', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->url(array('sort' => 'name')); $expected = '/posts/index/article%5Bpage%5D:3/article%5Bsort%5D:name'; $this->assertEquals($expected, $result); } /** * testOptions method * * @return void */ public function testOptions() { $this->Paginator->options('myDiv'); $this->assertEquals('myDiv', $this->Paginator->options['update']); $this->Paginator->options = array(); $this->Paginator->request->params = array(); $options = array('paging' => array('Article' => array( 'order' => 'desc', 'sort' => 'title' ))); $this->Paginator->options($options); $expected = array('Article' => array( 'order' => 'desc', 'sort' => 'title' )); $this->assertEquals($expected, $this->Paginator->request->params['paging']); $this->Paginator->options = array(); $this->Paginator->request->params = array(); $options = array('Article' => array( 'order' => 'desc', 'sort' => 'title' )); $this->Paginator->options($options); $this->assertEquals($expected, $this->Paginator->request->params['paging']); $options = array('paging' => array('Article' => array( 'order' => 'desc', 'sort' => 'Article.title' ))); $this->Paginator->options($options); $expected = array('Article' => array( 'order' => 'desc', 'sort' => 'Article.title' )); $this->assertEquals($expected, $this->Paginator->request->params['paging']); } /** * testPassedArgsMergingWithUrlOptions method * * @return void */ public function testPassedArgsMergingWithUrlOptions() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'pass' => array('2'), 'named' => array('foo' => 'bar'), 'url' => array('url' => 'articles/index/2/foo:bar')), array('base' => '/', 'here' => '/articles/', 'webroot' => '/') )); $this->Paginator->request->params['paging'] = array( 'Article' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 8, 'options' => array( 'page' => 1, 'order' => array(), 'conditions' => array() ), 'paramType' => 'named' ) ); $this->Paginator->request->params['pass'] = array(2); $this->Paginator->request->params['named'] = array('foo' => 'bar'); $this->Paginator->request->query = array('x' => 'y'); $this->Paginator->beforeRender('posts/index'); $result = $this->Paginator->sort('title'); $expected = array( 'a' => array('href' => '/articles/index/2/foo:bar/sort:title/direction:asc?x=y'), 'Title', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(); $expected = array( array('span' => array('class' => 'current')), '1', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/page:2/foo:bar?x=y')), '2', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/page:3/foo:bar?x=y')), '3', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/page:4/foo:bar?x=y')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/page:5/foo:bar?x=y')), '5', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/page:6/foo:bar?x=y')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/page:7/foo:bar?x=y')), '7', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/articles/index/2/page:2/foo:bar?x=y', 'rel' => 'next'), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); } /** * testPassedArgsMergingWithUrlOptionsParamTypeQuerystring method * * @return void */ public function testPassedArgsMergingWithUrlOptionsParamTypeQuerystring() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'pass' => array('2'), 'named' => array('foo' => 'bar'), 'url' => array('url' => 'articles/index/2/foo:bar')), array('base' => '/', 'here' => '/articles/', 'webroot' => '/') )); $this->Paginator->request->params['paging'] = array( 'Article' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 8, 'options' => array( 'page' => 1, 'order' => array(), 'conditions' => array() ), 'paramType' => 'querystring' ) ); $this->Paginator->request->params['pass'] = array(2); $this->Paginator->request->params['named'] = array('foo' => 'bar'); $this->Paginator->request->query = array('x' => 'y'); $this->Paginator->beforeRender('posts/index'); $result = $this->Paginator->sort('title'); $expected = array( 'a' => array('href' => '/articles/index/2/foo:bar?x=y&sort=title&direction=asc'), 'Title', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(); $expected = array( array('span' => array('class' => 'current')), '1', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar?x=y&page=2')), '2', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar?x=y&page=3')), '3', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar?x=y&page=4')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar?x=y&page=5')), '5', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar?x=y&page=6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/articles/index/2/foo:bar?x=y&page=7')), '7', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/articles/index/2/foo:bar?x=y&page=2', 'rel' => 'next'), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); } /** * testPagingLinks method * * @return void */ public function testPagingLinks() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); $expected = array( 'span' => array('class' => 'disabled'), '<< Previous', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled', 'tag' => 'div')); $expected = array( 'div' => array('class' => 'disabled'), '<< Previous', '/div' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Client']['page'] = 2; $this->Paginator->request->params['paging']['Client']['prevPage'] = true; $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/', 'rel' => 'prev'), '<< Previous', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', array('tag' => false), null, array('class' => 'disabled')); $expected = array( 'a' => array('href' => '/', 'rel' => 'prev', 'class' => 'prev'), '<< Previous', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev( '<< Previous', array(), null, array('disabledTag' => 'span', 'class' => 'disabled') ); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/', 'rel' => 'prev'), '<< Previous', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/index/page:3', 'rel' => 'next'), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next', array('tag' => 'li')); $expected = array( 'li' => array('class' => 'next'), 'a' => array('href' => '/index/page:3', 'rel' => 'next'), 'Next', '/a', '/li' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next', array('tag' => false)); $expected = array( 'a' => array('href' => '/index/page:3', 'rel' => 'next', 'class' => 'next'), 'Next', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', array('escape' => true)); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/', 'rel' => 'prev'), '<< Previous', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', array('escape' => false)); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/', 'rel' => 'prev'), 'preg:/<< Previous/', '/a', '/span' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 1, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->prev('', array('escape' => false), null, array('class' => 'prev disabled')); $expected = array( 'span' => array('class' => 'prev disabled'), 'i' => array('class' => 'fa fa-angle-left'), '/i', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('', array('escape' => false), null, array('escape' => true)); $expected = array( 'span' => array('class' => 'prev'), '<i class="fa fa-angle-left"></i>', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', null, 'Disabled'); $expected = array( 'span' => array('class' => 'prev'), '<strong>Disabled</strong>', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', null, 'Disabled', array('escape' => true)); $expected = array( 'span' => array('class' => 'prev'), '<strong>Disabled</strong>', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', null, 'Disabled', array('escape' => false)); $expected = array( 'span' => array('class' => 'prev'), 'assertTags($result, $expected); $result = $this->Paginator->prev('<< Previous', array('tag' => false), 'Disabled'); $expected = array( 'span' => array('class' => 'prev'), '<strong>Disabled</strong>', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev( '<< Previous', array('tag' => 'li'), null, array('tag' => 'li', 'disabledTag' => 'span', 'class' => 'disabled') ); $expected = array( 'li' => array('class' => 'disabled'), 'span' => array(), '<< Previous', '/span', '/li' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev( '<< Previous', array(), null, array('tag' => false, 'disabledTag' => 'span', 'class' => 'disabled') ); $expected = array( 'span' => array('class' => 'disabled'), '<< Previous', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, 'options' => array( 'page' => 1, 'limit' => 3, 'order' => array('Client.name' => 'DESC'), ), 'paramType' => 'named' ) ); $this->Paginator->request->params['paging']['Client']['page'] = 2; $this->Paginator->request->params['paging']['Client']['prevPage'] = true; $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); $expected = array( 'span' => array('class' => 'prev'), 'a' => array( 'href' => '/index/limit:3/sort:Client.name/direction:DESC', 'rel' => 'prev' ), '<< Previous', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array( 'href' => '/index/page:3/limit:3/sort:Client.name/direction:DESC', 'rel' => 'next' ), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 2, 'current' => 1, 'count' => 13, 'prevPage' => true, 'nextPage' => false, 'pageCount' => 2, 'options' => array( 'page' => 2, 'limit' => 10, 'order' => array(), 'conditions' => array() ), 'paramType' => 'named' ) ); $result = $this->Paginator->prev('Prev'); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/index/limit:10', 'rel' => 'prev'), 'Prev', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next', array(), null, array('tag' => false)); $expected = array( 'span' => array('class' => 'next'), 'Next', '/span' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 2, 'current' => 1, 'count' => 13, 'prevPage' => true, 'nextPage' => false, 'pageCount' => 2, 'defaults' => array(), 'options' => array( 'page' => 2, 'limit' => 10, 'order' => array(), 'conditions' => array() ), 'paramType' => 'named' ) ); $this->Paginator->options(array('url' => array(12, 'page' => 3))); $result = $this->Paginator->prev('Prev', array('url' => array('foo' => 'bar'))); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/index/12/limit:10/foo:bar', 'rel' => 'prev'), 'Prev', '/a', '/span' ); $this->assertTags($result, $expected); } /** * test that __pagingLink methods use $options when $disabledOptions is an empty value. * allowing you to use shortcut syntax * * @return void */ public function testPagingLinksOptionsReplaceEmptyDisabledOptions() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->prev('<< Previous', array('escape' => false)); $expected = array( 'span' => array('class' => 'prev'), 'preg:/<< Previous/', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next >>', array('escape' => false)); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/index/page:2', 'rel' => 'next'), 'preg:/Next >>/', '/a', '/span' ); $this->assertTags($result, $expected); } /** * testPagingLinksNotDefaultModel * * Test the creation of paging links when the non default model is used. * * @return void */ public function testPagingLinksNotDefaultModel() { // Multiple Model Paginate $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ), 'Server' => array( 'page' => 1, 'current' => 1, 'count' => 5, 'prevPage' => false, 'nextPage' => false, 'pageCount' => 5, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->sort('title', 'Title', array('model' => 'Client')); $expected = array( 'a' => array('href' => '/index/sort:title/direction:asc'), 'Title', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next', array('model' => 'Client')); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/index/page:2', 'rel' => 'next'), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next', array('model' => 'Server'), 'No Next', array('model' => 'Server')); $expected = array( 'span' => array('class' => 'next'), 'No Next', '/span' ); $this->assertTags($result, $expected); } /** * Test creating paging links for missing models. * * @return void */ public function testPagingLinksMissingModel() { $result = $this->Paginator->sort('title', 'Title', array('model' => 'Missing')); $expected = array( 'a' => array('href' => '/index/sort:title/direction:asc'), 'Title', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->next('Next', array('model' => 'Missing')); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/index/page:2', 'rel' => 'next'), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('Prev', array('model' => 'Missing')); $expected = array( 'span' => array('class' => 'prev'), 'Prev', '/span' ); $this->assertTags($result, $expected); } /** * testGenericLinks method * * @return void */ public function testGenericLinks() { $result = $this->Paginator->link('Sort by title on page 5', array('sort' => 'title', 'page' => 5, 'direction' => 'desc')); $expected = array( 'a' => array('href' => '/index/page:5/sort:title/direction:desc'), 'Sort by title on page 5', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['page'] = 2; $result = $this->Paginator->link('Sort by title', array('sort' => 'title', 'direction' => 'desc')); $expected = array( 'a' => array('href' => '/index/page:2/sort:title/direction:desc'), 'Sort by title', '/a' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['options']['page'] = 4; $result = $this->Paginator->link('Sort by title on page 4', array('sort' => 'Article.title', 'direction' => 'desc')); $expected = array( 'a' => array('href' => '/index/page:4/sort:Article.title/direction:desc'), 'Sort by title on page 4', '/a' ); $this->assertTags($result, $expected); } /** * Tests generation of generic links with preset options * * @return void */ public function testGenericLinksWithPresetOptions() { $result = $this->Paginator->link('Foo!', array('page' => 1)); $this->assertTags($result, array('a' => array('href' => '/'), 'Foo!', '/a')); $this->Paginator->options(array('sort' => 'title', 'direction' => 'desc')); $result = $this->Paginator->link('Foo!', array('page' => 1)); $this->assertTags($result, array( 'a' => array( 'href' => '/', 'sort' => 'title', 'direction' => 'desc' ), 'Foo!', '/a' )); $this->Paginator->options(array('sort' => null, 'direction' => null)); $result = $this->Paginator->link('Foo!', array('page' => 1)); $this->assertTags($result, array('a' => array('href' => '/'), 'Foo!', '/a')); $this->Paginator->options(array('url' => array( 'sort' => 'title', 'direction' => 'desc' ))); $result = $this->Paginator->link('Foo!', array('page' => 1)); $this->assertTags($result, array( 'a' => array('href' => '/index/sort:title/direction:desc'), 'Foo!', '/a' )); } /** * testNumbers method * * @return void */ public function testNumbers() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 8, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(); $expected = array( array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '8', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:10')), '10', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('tag' => 'li')); $expected = array( array('li' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/li', ' | ', array('li' => array('class' => 'current')), '8', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:10')), '10', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/li', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('tag' => 'li', 'separator' => false)); $expected = array( array('li' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/li', array('li' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/li', array('li' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/li', array('li' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/li', array('li' => array('class' => 'current')), '8', '/li', array('li' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/li', array('li' => array()), array('a' => array('href' => '/index/page:10')), '10', '/a', '/li', array('li' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/li', array('li' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/li', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(true); $expected = array( array('span' => array()), array('a' => array('href' => '/', 'rel' => 'first')), 'first', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '8', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:10')), '10', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:15', 'rel' => 'last')), 'last', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(); $expected = array( array('span' => array('class' => 'current')), '1', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 14, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(); $expected = array( array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:10')), '10', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:13')), '13', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '14', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:15')), '15', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 2, 'current' => 3, 'count' => 27, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 9, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('first' => 1, 'class' => 'page-link')); $expected = array( array('span' => array('class' => 'page-link')), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current page-link')), '2', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('first' => 1, 'currentClass' => 'active')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'active')), '2', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('first' => 1, 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'a')); $expected = array( array('li' => array()), array('a' => array('href' => '/')), '1', '/a', '/li', ' | ', array('li' => array('class' => 'active')), array('a' => array()), '2', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/li', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('first' => 1, 'class' => 'page-link', 'currentClass' => 'active')); $expected = array( array('span' => array('class' => 'page-link')), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'active page-link')), '2', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('last' => 1)); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '2', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 15, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('first' => 1)); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:10')), '10', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:13')), '13', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:14')), '14', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '15', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 10, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 3, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $options = array('modulus' => 10); $result = $this->Paginator->numbers($options); $expected = array( array('span' => array('class' => 'current')), '1', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('modulus' => 3, 'currentTag' => 'span', 'tag' => 'li')); $expected = array( array('li' => array('class' => 'current')), array('span' => array()), '1', '/span', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/li', ' | ', array('li' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/li', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 2, 'current' => 10, 'count' => 31, 'prevPage' => true, 'nextPage' => true, 'pageCount' => 4, 'options' => array( 'page' => 1, 'order' => array('Client.name' => 'DESC'), ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('class' => 'page-link')); $expected = array( array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/sort:Client.name/direction:DESC')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current page-link')), '2', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:3/sort:Client.name/direction:DESC')), '3', '/a', '/span', ' | ', array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:4/sort:Client.name/direction:DESC')), '4', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 2, 'current' => 2, 'count' => 30, 'prevPage' => false, 'nextPage' => 3, 'pageCount' => 3, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $request = new CakeRequest(); $request->addParams(array( 'controller' => 'clients', 'action' => 'index', 'plugin' => null, 'page' => 2 )); $request->base = ''; $request->here = '/clients/index/page:2'; $request->webroot = '/'; Router::setRequestInfo($request); $result = $this->Paginator->numbers(); $expected = array( array('span' => array()), array('a' => array('href' => '/clients')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '2', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/clients/index/page:3')), '3', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 2, 'current' => 2, 'count' => 30, 'prevPage' => false, 'nextPage' => 3, 'pageCount' => 3, 'options' => array( 'page' => 1, ), 'paramType' => 'querystring' ) ); $request = new CakeRequest(); $request->addParams(array( 'controller' => 'clients', 'action' => 'index', 'plugin' => null )); $request->base = ''; $request->here = '/clients?page=2'; $request->webroot = '/'; Router::setRequestInfo($request); $result = $this->Paginator->numbers(); $expected = array( array('span' => array()), array('a' => array('href' => '/clients')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '2', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/clients?page=3')), '3', '/a', '/span', ); $this->assertTags($result, $expected); } /** * Test that numbers() works with first and last options. * * @return void */ public function testNumbersFirstAndLast() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 10, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '10', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:13')), '13', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:14')), '14', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:15')), '15', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 6, 'current' => 15, 'count' => 623, 'prevPage' => 1, 'nextPage' => 1, 'pageCount' => 42, 'options' => array( 'page' => 6, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '6', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:10')), '10', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:42')), '42', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 37, 'current' => 15, 'count' => 623, 'prevPage' => 1, 'nextPage' => 1, 'pageCount' => 42, 'options' => array( 'page' => 37, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:33')), '33', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:34')), '34', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:35')), '35', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:36')), '36', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '37', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:38')), '38', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:39')), '39', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:40')), '40', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:41')), '41', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:42')), '42', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 4895, 'current' => 10, 'count' => 48962, 'prevPage' => 1, 'nextPage' => 1, 'pageCount' => 4897, 'options' => array( 'page' => 4894, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2)); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:4894')), '4894', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '4895', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Client']['page'] = 3; $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2)); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '3', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2, 'separator' => ' - ')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', array('span' => array('class' => 'current')), '3', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 5, 'last' => 5, 'separator' => ' - ')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', array('span' => array('class' => 'current')), '3', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:4893')), '4893', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4894')), '4894', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4895')), '4895', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Client']['page'] = 4893; $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 4, 'last' => 5, 'separator' => ' - ')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:4891')), '4891', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4892')), '4892', '/a', '/span', ' - ', array('span' => array('class' => 'current')), '4893', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4894')), '4894', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4895')), '4895', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Client']['page'] = 58; $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 4, 'last' => 5, 'separator' => ' - ')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:5')), '5', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:56')), '56', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:57')), '57', '/a', '/span', ' - ', array('span' => array('class' => 'current')), '58', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:59')), '59', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:60')), '60', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:4893')), '4893', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4894')), '4894', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4895')), '4895', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Client']['page'] = 5; $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 4, 'last' => 5, 'separator' => ' - ')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:3')), '3', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' - ', array('span' => array('class' => 'current')), '5', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:4893')), '4893', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4894')), '4894', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4895')), '4895', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Client']['page'] = 3; $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2, 'separator' => ' - ', 'ellipsis' => ' ~~~ ')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', array('span' => array('class' => 'current')), '3', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' ~~~ ', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Client']['page'] = 3; $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2, 'separator' => ' - ', 'ellipsis' => '...')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', array('span' => array('class' => 'current')), '3', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', array('span' => array('class' => 'ellipsis')), '...', '/span', array('span' => array()), array('a' => array('href' => '/index/page:4896')), '4896', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); } /** * Test first/last options as strings. * * @return void */ public function testNumbersStringFirstAndLast() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 10, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( 'page' => 1, ), 'paramType' => 'named' ) ); $result = $this->Paginator->numbers(array('first' => '1', 'last' => '1')); $expected = array( array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:8')), '8', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:9')), '9', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '10', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:11')), '11', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:12')), '12', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:13')), '13', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:14')), '14', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:15')), '15', '/a', '/span', ); $this->assertTags($result, $expected); } /** * test first() and last() with tag options * * @return void */ public function testFirstAndLastTag() { $result = $this->Paginator->first('<<', array('tag' => 'li', 'class' => 'first')); $expected = array( 'li' => array('class' => 'first'), 'a' => array('href' => '/', 'rel' => 'first'), '<<', '/a', '/li' ); $this->assertTags($result, $expected); $result = $this->Paginator->last(2, array('tag' => 'li', 'class' => 'last')); $expected = array( '...', 'li' => array('class' => 'last'), array('a' => array('href' => '/index/page:6')), '6', '/a', '/li', ' | ', array('li' => array('class' => 'last')), array('a' => array('href' => '/index/page:7')), '7', '/a', '/li', ); $this->assertTags($result, $expected); $result = $this->Paginator->last('2', array('tag' => 'li', 'class' => 'last')); $this->assertTags($result, $expected); } /** * test that on the last page you don't get a link ot the last page. * * @return void */ public function testLastNoOutput() { $this->Paginator->request->params['paging']['Article']['page'] = 15; $this->Paginator->request->params['paging']['Article']['pageCount'] = 15; $result = $this->Paginator->last(); $expected = ''; $this->assertEquals($expected, $result); } /** * test first() on the first page. * * @return void */ public function testFirstEmpty() { $this->Paginator->request->params['paging']['Article']['page'] = 1; $result = $this->Paginator->first(); $expected = ''; $this->assertEquals($expected, $result); } /** * test first() and options() * * @return void */ public function testFirstFullBaseUrl() { $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'DESC'); $this->Paginator->options(array('url' => array('full_base' => true))); $result = $this->Paginator->first(); $expected = array( ' array( 'href' => FULL_BASE_URL . '/index/sort:Article.title/direction:DESC', 'rel' => 'first' )), '<< first', '/a', '/span', ); $this->assertTags($result, $expected); } /** * test first() on the fence-post * * @return void */ public function testFirstBoundaries() { $result = $this->Paginator->first(); $expected = array( ' array('href' => '/', 'rel' => 'first'), '<< first', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->first(2); $expected = array( ' array('href' => '/')), '1', '/a', '/span', ' | ', ' array('href' => '/index/page:2')), '2', '/a', '/span' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['page'] = 2; $result = $this->Paginator->first(3); $this->assertEquals('', $result, 'When inside the first links range, no links should be made'); } /** * test params() method * * @return void */ public function testParams() { $result = $this->Paginator->params(); $this->assertArrayHasKey('page', $result); $this->assertArrayHasKey('pageCount', $result); } /** * test param() method * * @return void */ public function testParam() { $result = $this->Paginator->param('count'); $this->assertSame(62, $result); $result = $this->Paginator->param('imaginary'); $this->assertNull($result); } /** * test last() method * * @return void */ public function testLast() { $result = $this->Paginator->last(); $expected = array( ' array('href' => '/index/page:7', 'rel' => 'last'), 'last >>', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->last(1); $expected = array( '...', ' array('href' => '/index/page:7'), '7', '/a', '/span' ); $this->assertTags($result, $expected); $this->Paginator->request->params['paging']['Article']['page'] = 6; $result = $this->Paginator->last(2); $expected = array( '...', ' array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', ' array('href' => '/index/page:7')), '7', '/a', '/span', ); $this->assertTags($result, $expected); // Test stringy number. $result = $this->Paginator->last('2'); $this->assertTags($result, $expected); $result = $this->Paginator->last(3); $this->assertEquals('', $result, 'When inside the last links range, no links should be made'); } /** * undocumented function * * @return void */ public function testLastOptions() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 4, 'current' => 3, 'count' => 30, 'prevPage' => false, 'nextPage' => 2, 'pageCount' => 15, 'options' => array( 'page' => 1, 'order' => array('Client.name' => 'DESC'), ), 'paramType' => 'named' ) ); $result = $this->Paginator->last(); $expected = array( ' array( 'href' => '/index/page:15/sort:Client.name/direction:DESC', 'rel' => 'last' )), 'last >>', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->last(1); $expected = array( '...', ' array('href' => '/index/page:15/sort:Client.name/direction:DESC')), '15', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->last(2); $expected = array( '...', ' array('href' => '/index/page:14/sort:Client.name/direction:DESC')), '14', '/a', '/span', ' | ', ' array('href' => '/index/page:15/sort:Client.name/direction:DESC')), '15', '/a', '/span', ); $this->assertTags($result, $expected); $result = $this->Paginator->last(2, array('ellipsis' => '...')); $expected = array( array('span' => array('class' => 'ellipsis')), '...', '/span', ' array('href' => '/index/page:14/sort:Client.name/direction:DESC')), '14', '/a', '/span', ' | ', ' array('href' => '/index/page:15/sort:Client.name/direction:DESC')), '15', '/a', '/span', ); $this->assertTags($result, $expected); } /** * testCounter method * * @return void */ public function testCounter() { $this->Paginator->request->params['paging'] = array( 'Client' => array( 'page' => 1, 'current' => 3, 'count' => 13, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 5, 'limit' => 3, 'options' => array( 'page' => 1, 'order' => array('Client.name' => 'DESC'), ), 'paramType' => 'named' ) ); $input = 'Page %page% of %pages%, showing %current% records out of %count% total, '; $input .= 'starting on record %start%, ending on %end%'; $result = $this->Paginator->counter($input); $expected = 'Page 1 of 5, showing 3 records out of 13 total, starting on record 1, '; $expected .= 'ending on 3'; $this->assertEquals($expected, $result); $input = 'Page {:page} of {:pages}, showing {:current} records out of {:count} total, '; $input .= 'starting on record {:start}, ending on {:end}'; $result = $this->Paginator->counter($input); $this->assertEquals($expected, $result); $input = 'Page %page% of %pages%'; $result = $this->Paginator->counter($input); $expected = 'Page 1 of 5'; $this->assertEquals($expected, $result); $result = $this->Paginator->counter(array('format' => $input)); $expected = 'Page 1 of 5'; $this->assertEquals($expected, $result); $result = $this->Paginator->counter(array('format' => 'pages')); $expected = '1 of 5'; $this->assertEquals($expected, $result); $result = $this->Paginator->counter(array('format' => 'range')); $expected = '1 - 3 of 13'; $this->assertEquals($expected, $result); $result = $this->Paginator->counter('Showing %page% of %pages% %model%'); $this->assertEquals('Showing 1 of 5 clients', $result); } /** * testHasPage method * * @return void */ public function testHasPage() { $result = $this->Paginator->hasPage('Article', 15); $this->assertFalse($result); $result = $this->Paginator->hasPage('UndefinedModel', 2); $this->assertFalse($result); $result = $this->Paginator->hasPage('Article', 2); $this->assertTrue($result); $result = $this->Paginator->hasPage(2); $this->assertTrue($result); } /** * testWithPlugin method * * @return void */ public function testWithPlugin() { Router::reload(); Router::setRequestInfo(array( array( 'pass' => array(), 'named' => array(), 'prefix' => null, 'form' => array(), 'controller' => 'magazines', 'plugin' => 'my_plugin', 'action' => 'index', 'url' => array('ext' => 'html', 'url' => 'my_plugin/magazines')), array('base' => '', 'here' => '/my_plugin/magazines', 'webroot' => '/') )); $result = $this->Paginator->link('Page 3', array('page' => 3)); $expected = array( 'a' => array('href' => '/my_plugin/magazines/index/page:3'), 'Page 3', '/a' ); $this->assertTags($result, $expected); $this->Paginator->options(array('url' => array('action' => 'another_index'))); $result = $this->Paginator->link('Page 3', array('page' => 3)); $expected = array( 'a' => array('href' => '/my_plugin/magazines/another_index/page:3'), 'Page 3', '/a' ); $this->assertTags($result, $expected); $this->Paginator->options(array('url' => array('controller' => 'issues'))); $result = $this->Paginator->link('Page 3', array('page' => 3)); $expected = array( 'a' => array('href' => '/my_plugin/issues/index/page:3'), 'Page 3', '/a' ); $this->assertTags($result, $expected); $this->Paginator->options(array('url' => array('plugin' => null))); $result = $this->Paginator->link('Page 3', array('page' => 3)); $expected = array( 'a' => array('href' => '/magazines/index/page:3'), 'Page 3', '/a' ); $this->assertTags($result, $expected); $this->Paginator->options(array('url' => array('plugin' => null, 'controller' => 'issues'))); $result = $this->Paginator->link('Page 3', array('page' => 3)); $expected = array( 'a' => array('href' => '/issues/index/page:3'), 'Page 3', '/a' ); $this->assertTags($result, $expected); } /** * testNextLinkUsingDotNotation method * * @return void */ public function testNextLinkUsingDotNotation() { Router::reload(); Router::parse('/'); Router::setRequestInfo(array( array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'url' => array('url' => 'accounts/')), array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/', 'passedArgs' => array()) )); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['page'] = 1; $test = array('url' => array( 'page' => '1', 'sort' => 'Article.title', 'direction' => 'asc', )); $this->Paginator->options($test); $result = $this->Paginator->next('Next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array( 'href' => '/officespace/accounts/index/page:2/sort:Article.title/direction:asc', 'rel' => 'next' ), 'Next', '/a', '/span', ); $this->assertTags($result, $expected); } /** * Ensure that the internal link class object is called when the update key is present * * @return void */ public function testAjaxLinkGenerationNumbers() { $this->Paginator->Js->expectCallCount('link', 2); $this->Paginator->numbers(array( 'modulus' => '2', 'url' => array('controller' => 'projects', 'action' => 'sort'), 'update' => 'list' )); } /** * test that paginatorHelper::link() uses JsHelper to make links when 'update' key is present * * @return void */ public function testAjaxLinkGenerationLink() { $this->Paginator->Js->expects($this->once()) ->method('link') ->will($this->returnValue('I am a link')); $result = $this->Paginator->link('test', array('controller' => 'posts'), array('update' => '#content')); $this->assertEquals('I am a link', $result); } /** * test that mock classes injected into paginatorHelper are called when using link() * * @expectedException CakeException * @return void */ public function testMockAjaxProviderClassInjection() { $mock = $this->getMock('PaginatorHelper', array(), array($this->View), 'PaginatorMockJsHelper'); $Paginator = new PaginatorHelper($this->View, array('ajax' => 'PaginatorMockJs')); $Paginator->request->params['paging'] = array( 'Article' => array( 'current' => 9, 'count' => 62, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 7, 'defaults' => array(), 'options' => array(), 'paramType' => 'named' ) ); $Paginator->PaginatorMockJs = $mock; $Paginator->PaginatorMockJs->expects($this->once())->method('link'); $Paginator->link('Page 2', array('page' => 2), array('update' => '#content')); new PaginatorHelper($this->View, array('ajax' => 'Form')); } /** * test that query string URLs can be generated. * * @return void */ public function testQuerystringUrlGeneration() { $this->Paginator->request->params['paging']['Article']['paramType'] = 'querystring'; $result = $this->Paginator->url(array('page' => '1')); $expected = '/'; $this->assertEquals($expected, $result); $result = $this->Paginator->url(array('page' => '1', 'limit' => 10, 'something' => 'else')); $expected = '/index/something:else?limit=10'; $this->assertEquals($expected, $result); $result = $this->Paginator->url(array('page' => '4')); $expected = '/?page=4'; $this->assertEquals($expected, $result); $result = $this->Paginator->url(array('page' => '4', 'limit' => 10, 'something' => 'else')); $expected = '/index/something:else?page=4&limit=10'; $this->assertEquals($expected, $result); } /** * test query string paging link. * * @return void */ public function testQuerystringNextAndPrev() { $this->Paginator->request->params['paging']['Article']['paramType'] = 'querystring'; $this->Paginator->request->params['paging']['Article']['page'] = 2; $this->Paginator->request->params['paging']['Article']['nextPage'] = true; $this->Paginator->request->params['paging']['Article']['prevPage'] = true; $result = $this->Paginator->next('Next'); $expected = array( 'span' => array('class' => 'next'), 'a' => array('href' => '/?page=3', 'rel' => 'next'), 'Next', '/a', '/span' ); $this->assertTags($result, $expected); $result = $this->Paginator->prev('Prev'); $expected = array( 'span' => array('class' => 'prev'), 'a' => array('href' => '/', 'rel' => 'prev'), 'Prev', '/a', '/span' ); $this->assertTags($result, $expected); } /** * test that additional keys can be flagged as query string args. * * @return void */ public function testOptionsConvertKeys() { $this->Paginator->options(array( 'convertKeys' => array('something'), 'Article' => array('paramType' => 'querystring') )); $result = $this->Paginator->url(array('page' => '4', 'something' => 'bar')); $expected = '/?page=4&something=bar'; $this->assertEquals($expected, $result); } /** * test the current() method * * @return void */ public function testCurrent() { $result = $this->Paginator->current(); $this->assertEquals($this->Paginator->request->params['paging']['Article']['page'], $result); $result = $this->Paginator->current('Incorrect'); $this->assertEquals(1, $result); } /** * test the defaultModel() method * * @return void */ public function testNoDefaultModel() { $this->Paginator->request = new CakeRequest(null, false); $this->assertNull($this->Paginator->defaultModel()); } /** * test the defaultModel() method * * @return void */ public function testDefaultModel() { $this->Paginator->request = new CakeRequest(null, false); $this->Paginator->defaultModel('Article'); $this->assertEquals('Article', $this->Paginator->defaultModel()); $this->Paginator->options(array('model' => 'Client')); $this->assertEquals('Client', $this->Paginator->defaultModel()); } /** * test the numbers() method when there is only one page * * @return void */ public function testWithOnePage() { $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 1, 'current' => 2, 'count' => 2, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 1, 'options' => array( 'page' => 1, ), 'paramType' => 'named', ) ); $this->assertSame('', $this->Paginator->numbers()); $this->assertSame('', $this->Paginator->first()); $this->assertSame('', $this->Paginator->last()); } /** * test the numbers() method when there is only one page * * @return void */ public function testWithZeroPages() { $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 0, 'current' => 0, 'count' => 0, 'prevPage' => false, 'nextPage' => false, 'pageCount' => 0, 'limit' => 10, 'options' => array( 'page' => 0, 'conditions' => array() ), 'paramType' => 'named', ) ); $result = $this->Paginator->counter(array('format' => 'pages')); $expected = '0 of 1'; $this->assertEquals($expected, $result); } /** * Verify that no next and prev links are created for single page results * * @return void */ public function testMetaPage0() { $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 1, 'prevPage' => false, 'nextPage' => false, 'pageCount' => 1, ) ); $expected = ''; $result = $this->Paginator->meta(); $this->assertSame($expected, $result); } /** * Verify that page 1 only has a next link * * @return void */ public function testMetaPage1() { $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 1, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 2, 'options' => array(), 'paramType' => 'querystring' ) ); $expected = ''; $result = $this->Paginator->meta(); $this->assertSame($expected, $result); } /** * Verify that the method will append to a block * * @return void */ public function testMetaPage1InlineFalse() { $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 1, 'prevPage' => false, 'nextPage' => true, 'pageCount' => 2, 'options' => array(), 'paramType' => 'querystring' ) ); $expected = ''; $this->Paginator->meta(array('block' => true)); $result = $this->View->fetch('meta'); $this->assertSame($expected, $result); } /** * Verify that the last page only has a prev link * * @return void */ public function testMetaPage1Last() { $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 2, 'prevPage' => true, 'nextPage' => false, 'pageCount' => 2, 'options' => array(), 'paramType' => 'querystring' ) ); $expected = ''; $result = $this->Paginator->meta(); $this->assertSame($expected, $result); } /** * Verify that a page in the middle has both links * * @return void */ public function testMetaPage10Last() { $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 5, 'prevPage' => true, 'nextPage' => true, 'pageCount' => 10, 'options' => array(), 'paramType' => 'querystring' ) ); $expected = ''; $expected .= ''; $result = $this->Paginator->meta(); $this->assertSame($expected, $result); } /** * Verify that meta() uses URL options * * @return void */ public function testMetaPageUrlOptions() { $this->Paginator->options(array( 'url' => array('?' => array('a' => 'b')) )); $this->Paginator->request['paging'] = array( 'Article' => array( 'page' => 5, 'prevPage' => true, 'nextPage' => true, 'pageCount' => 10, 'options' => array(), 'paramType' => 'querystring' ) ); $expected = ''; $expected .= ''; $result = $this->Paginator->meta(); $this->assertSame($expected, $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/View/Helper/MootoolsEngineHelperTest.php0000644000000000000000000003004113365153155026710 0ustar rootrootView = $this->getMock('View', array('addScript'), array(&$controller)); $this->Moo = new MootoolsEngineHelper($this->View); } /** * tearDown * * @return void */ public function tearDown() { parent::tearDown(); unset($this->Moo); } /** * test selector method * * @return void */ public function testSelector() { $result = $this->Moo->get('#content'); $this->assertEquals($this->Moo, $result); $this->assertEquals($this->Moo->selection, '$("content")'); $result = $this->Moo->get('a .remove'); $this->assertEquals($this->Moo, $result); $this->assertEquals($this->Moo->selection, '$$("a .remove")'); $result = $this->Moo->get('document'); $this->assertEquals($this->Moo, $result); $this->assertEquals($this->Moo->selection, "$(document)"); $result = $this->Moo->get('window'); $this->assertEquals($this->Moo, $result); $this->assertEquals($this->Moo->selection, "$(window)"); $result = $this->Moo->get('ul'); $this->assertEquals($this->Moo, $result); $this->assertEquals($this->Moo->selection, '$$("ul")'); $result = $this->Moo->get('#some_long-id.class'); $this->assertEquals($this->Moo, $result); $this->assertEquals($this->Moo->selection, '$$("#some_long-id.class")'); } /** * test event binding * * @return void */ public function testEvent() { $this->Moo->get('#myLink'); $result = $this->Moo->event('click', 'doClick', array('wrap' => false)); $expected = '$("myLink").addEvent("click", doClick);'; $this->assertEquals($expected, $result); $result = $this->Moo->event('click', 'this.setStyle("display", "");', array('stop' => false)); $expected = '$("myLink").addEvent("click", function (event) {this.setStyle("display", "");});'; $this->assertEquals($expected, $result); $result = $this->Moo->event('click', 'this.setStyle("display", "none");'); $expected = "\$(\"myLink\").addEvent(\"click\", function (event) {event.stop();\nthis.setStyle(\"display\", \"none\");});"; $this->assertEquals($expected, $result); } /** * test dom ready event creation * * @return void */ public function testDomReady() { $result = $this->Moo->domReady('foo.name = "bar";'); $expected = 'window.addEvent("domready", function (event) {foo.name = "bar";});'; $this->assertEquals($expected, $result); } /** * test Each method * * @return void */ public function testEach() { $this->Moo->get('#foo'); $result = $this->Moo->each('item.setStyle("display", "none");'); $expected = '$("foo").each(function (item, index) {item.setStyle("display", "none");});'; $this->assertEquals($expected, $result); } /** * test Effect generation * * @return void */ public function testEffect() { $this->Moo->get('#foo'); $result = $this->Moo->effect('show'); $expected = '$("foo").setStyle("display", "");'; $this->assertEquals($expected, $result); $result = $this->Moo->effect('hide'); $expected = '$("foo").setStyle("display", "none");'; $this->assertEquals($expected, $result); $result = $this->Moo->effect('fadeIn'); $expected = '$("foo").fade("in");'; $this->assertEquals($expected, $result); $result = $this->Moo->effect('fadeOut'); $expected = '$("foo").fade("out");'; $this->assertEquals($expected, $result); $result = $this->Moo->effect('slideIn'); $expected = '$("foo").slide("in");'; $this->assertEquals($expected, $result); $result = $this->Moo->effect('slideOut'); $expected = '$("foo").slide("out");'; $this->assertEquals($expected, $result); $result = $this->Moo->effect('slideOut', array('speed' => 'fast')); $expected = '$("foo").set("slide", {duration:"short"}).slide("out");'; $this->assertEquals($expected, $result); $result = $this->Moo->effect('slideOut', array('speed' => 'slow')); $expected = '$("foo").set("slide", {duration:"long"}).slide("out");'; $this->assertEquals($expected, $result); } /** * Test Request Generation * * @return void */ public function testRequest() { $result = $this->Moo->request(array('controller' => 'posts', 'action' => 'view', 1)); $expected = 'var jsRequest = new Request({url:"\\/posts\\/view\\/1"}).send();'; $this->assertEquals($expected, $result); $result = $this->Moo->request('/posts/view/1', array('update' => 'content')); $expected = 'var jsRequest = new Request.HTML({update:"content", url:"\\/posts\\/view\\/1"}).send();'; $this->assertEquals($expected, $result); $result = $this->Moo->request('/people/edit/1', array( 'method' => 'post', 'complete' => 'doSuccess', 'error' => 'handleError', 'type' => 'json', 'data' => array('name' => 'jim', 'height' => '185cm'), 'wrapCallbacks' => false )); $expected = 'var jsRequest = new Request.JSON({method:"post", onComplete:doSuccess, onFailure:handleError, url:"\\/people\\/edit\\/1"}).send({"name":"jim","height":"185cm"});'; $this->assertEquals($expected, $result); $result = $this->Moo->request('/people/edit/1', array( 'method' => 'post', 'complete' => 'doSuccess', 'update' => '#update-zone', 'wrapCallbacks' => false )); $expected = 'var jsRequest = new Request.HTML({method:"post", onComplete:doSuccess, update:"update-zone", url:"\\/people\\/edit\\/1"}).send();'; $this->assertEquals($expected, $result); $result = $this->Moo->request('/people/edit/1', array( 'method' => 'post', 'complete' => 'doComplete', 'success' => 'doSuccess', 'error' => 'doFailure', 'before' => 'doBefore', 'update' => 'update-zone', 'wrapCallbacks' => false )); $expected = 'var jsRequest = new Request.HTML({method:"post", onComplete:doComplete, onFailure:doFailure, onRequest:doBefore, onSuccess:doSuccess, update:"update-zone", url:"\\/people\\/edit\\/1"}).send();'; $this->assertEquals($expected, $result); $result = $this->Moo->request('/people/edit/1', array( 'method' => 'post', 'complete' => 'doComplete', 'success' => 'doSuccess', 'error' => 'doFailure', 'before' => 'doBefore', 'update' => 'update-zone', 'dataExpression' => true, 'data' => '$("foo").toQueryString()', 'wrapCallbacks' => false )); $expected = 'var jsRequest = new Request.HTML({method:"post", onComplete:doComplete, onFailure:doFailure, onRequest:doBefore, onSuccess:doSuccess, update:"update-zone", url:"\\/people\\/edit\\/1"}).send($("foo").toQueryString());'; $this->assertEquals($expected, $result); $result = $this->Moo->request('/people/edit/1', array( 'method' => 'post', 'before' => 'doBefore', 'success' => 'doSuccess', 'complete' => 'doComplete', 'update' => '#update-zone', )); $expected = 'var jsRequest = new Request.HTML({method:"post", onComplete:function () {doComplete}, onRequest:function () {doBefore}, onSuccess:function (responseText, responseXML) {doSuccess}, update:"update-zone", url:"\\/people\\/edit\\/1"}).send();'; $this->assertEquals($expected, $result); } /** * test sortable list generation * * @return void */ public function testSortable() { $this->Moo->get('#myList'); $result = $this->Moo->sortable(array( 'distance' => 5, 'containment' => 'parent', 'start' => 'onStart', 'complete' => 'onStop', 'sort' => 'onSort', 'wrapCallbacks' => false )); $expected = 'var jsSortable = new Sortables($("myList"), {constrain:"parent", onComplete:onStop, onSort:onSort, onStart:onStart, snap:5});'; $this->assertEquals($expected, $result); } /** * test drag() method * * @return void */ public function testDrag() { $this->Moo->get('#drag-me'); $result = $this->Moo->drag(array( 'start' => 'onStart', 'drag' => 'onDrag', 'stop' => 'onStop', 'snapGrid' => array(10, 10), 'wrapCallbacks' => false )); $expected = '$("drag-me").makeDraggable({onComplete:onStop, onDrag:onDrag, onStart:onStart, snap:[10,10]});'; $this->assertEquals($expected, $result); } /** * test drop() method with the required drag option missing * * @expectedException PHPUnit_Framework_Error_Warning * @return void */ public function testDropWithMissingOption() { $this->Moo->get('#drop-me'); $this->Moo->drop(array( 'drop' => 'onDrop', 'leave' => 'onLeave', 'hover' => 'onHover', )); } /** * test drop() method * * @return void */ public function testDrop() { $this->Moo->get('#drop-me'); $result = $this->Moo->drop(array( 'drop' => 'onDrop', 'leave' => 'onLeave', 'hover' => 'onHover', 'drag' => '#my-drag', 'wrapCallbacks' => false )); $expected = '$("my-drag").makeDraggable({droppables:$("drop-me"), onDrop:onDrop, onEnter:onHover, onLeave:onLeave});'; $this->assertEquals($expected, $result); $this->assertEquals($this->Moo->selection, '$("drop-me")'); $result = $this->Moo->drop(array( 'drop' => 'onDrop', 'leave' => 'onLeave', 'hover' => 'onHover', 'drag' => '#my-drag', )); $expected = '$("my-drag").makeDraggable({droppables:$("drop-me"), onDrop:function (element, droppable, event) {onDrop}, onEnter:function (element, droppable) {onHover}, onLeave:function (element, droppable) {onLeave}});'; $this->assertEquals($expected, $result); } /** * test slider generation * * @return void */ public function testSlider() { $this->Moo->get('#slider'); $result = $this->Moo->slider(array( 'handle' => '#my-handle', 'complete' => 'onComplete', 'change' => 'onChange', 'direction' => 'horizontal', 'wrapCallbacks' => false )); $expected = 'var jsSlider = new Slider($("slider"), $("my-handle"), {mode:"horizontal", onChange:onChange, onComplete:onComplete});'; $this->assertEquals($expected, $result); $this->assertEquals($this->Moo->selection, '$("slider")'); $this->Moo->get('#slider'); $result = $this->Moo->slider(array( 'handle' => '#my-handle', 'complete' => 'onComplete', 'change' => 'onChange', 'direction' => 'horizontal', 'min' => 10, 'max' => 40, 'wrapCallbacks' => false )); $expected = 'var jsSlider = new Slider($("slider"), $("my-handle"), {mode:"horizontal", onChange:onChange, onComplete:onComplete, range:[10,40]});'; $this->assertEquals($expected, $result); $this->Moo->get('#slider'); $result = $this->Moo->slider(array( 'handle' => '#my-handle', 'complete' => 'complete;', 'change' => 'change;', 'direction' => 'horizontal', )); $expected = 'var jsSlider = new Slider($("slider"), $("my-handle"), {mode:"horizontal", onChange:function (step) {change;}, onComplete:function (event) {complete;}});'; $this->assertEquals($expected, $result); } /** * test the serializeForm implementation. * * @return void */ public function testSerializeForm() { $this->Moo->get('#element'); $result = $this->Moo->serializeForm(array('isForm' => true)); $expected = '$("element").toQueryString();'; $this->assertEquals($expected, $result); $result = $this->Moo->serializeForm(array('isForm' => true, 'inline' => true)); $expected = '$("element").toQueryString()'; $this->assertEquals($expected, $result); $result = $this->Moo->serializeForm(array('isForm' => false)); $expected = '$($("element").form).toQueryString();'; $this->assertEquals($expected, $result); $result = $this->Moo->serializeForm(array('isForm' => false, 'inline' => true)); $expected = '$($("element").form).toQueryString()'; $this->assertEquals($expected, $result); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/View/Helper/NumberHelperTest.php0000644000000000000000000000533413365153155025206 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.View.Helper * @since CakePHP(tm) v 1.2.0.4206 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('View', 'View'); App::uses('NumberHelper', 'View/Helper'); /** * NumberHelperTestObject class */ class NumberHelperTestObject extends NumberHelper { public function attach(CakeNumberMock $cakeNumber) { $this->_engine = $cakeNumber; } public function engine() { return $this->_engine; } } /** * CakeNumberMock class */ class CakeNumberMock { } /** * NumberHelperTest class * * @package Cake.Test.Case.View.Helper */ class NumberHelperTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->View = new View(null); } /** * tearDown method * * @return void */ public function tearDown() { parent::tearDown(); unset($this->View); } /** * test CakeNumber class methods are called correctly * * @return void */ public function testNumberHelperProxyMethodCalls() { $methods = array( 'precision', 'toReadableSize', 'toPercentage', 'format', 'currency', 'addFormat', ); $CakeNumber = $this->getMock('CakeNumberMock', $methods); $Number = new NumberHelperTestObject($this->View, array('engine' => 'CakeNumberMock')); $Number->attach($CakeNumber); foreach ($methods as $method) { $CakeNumber->expects($this->at(0))->method($method); $Number->{$method}('who', 'what', 'when', 'where', 'how'); } } /** * test engine override * * @return void */ public function testEngineOverride() { App::build(array( 'Utility' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Utility' . DS) ), App::REGISTER); $Number = new NumberHelperTestObject($this->View, array('engine' => 'TestAppEngine')); $this->assertInstanceOf('TestAppEngine', $Number->engine()); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) )); CakePlugin::load('TestPlugin'); $Number = new NumberHelperTestObject($this->View, array('engine' => 'TestPlugin.TestPluginEngine')); $this->assertInstanceOf('TestPluginEngine', $Number->engine()); CakePlugin::unload('TestPlugin'); } } ZoneMinder-1.32.2/web/api/lib/Cake/Test/Case/View/Helper/TextHelperTest.php0000644000000000000000000004663113365153155024707 0ustar rootroot * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests * @package Cake.Test.Case.View.Helper * @since CakePHP(tm) v 1.2.0.4206 * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('View', 'View'); App::uses('TextHelper', 'View/Helper'); /** * TextHelperTestObject * * @package Cake.Test.Case.View.Helper */ class TextHelperTestObject extends TextHelper { public function attach(CakeTextMock $string) { $this->_engine = $string; } public function engine() { return $this->_engine; } } /** * CakeTextMock class * * @package Cake.Test.Case.View.Helper */ class CakeTextMock { } /** * TextHelperTest class * * @package Cake.Test.Case.View.Helper */ class TextHelperTest extends CakeTestCase { /** * setUp method * * @return void */ public function setUp() { parent::setUp(); $this->View = new View(null); $this->Text = new TextHelper($this->View); } /** * tearDown method * * @return void */ public function tearDown() { unset($this->View); parent::tearDown(); } /** * test String class methods are called correctly * * @return void */ public function testTextHelperProxyMethodCalls() { $methods = array( 'highlight', 'stripLinks', 'truncate', 'tail', 'excerpt', 'toList', ); $CakeText = $this->getMock('CakeTextMock', $methods); $Text = new TextHelperTestObject($this->View, array('engine' => 'CakeTextMock')); $Text->attach($CakeText); foreach ($methods as $method) { $CakeText->expects($this->at(0))->method($method); $Text->{$method}('who', 'what', 'when', 'where', 'how'); } } /** * test engine override * * @return void */ public function testEngineOverride() { App::build(array( 'Utility' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Utility' . DS) ), App::REGISTER); $Text = new TextHelperTestObject($this->View, array('engine' => 'TestAppEngine')); $this->assertInstanceOf('TestAppEngine', $Text->engine()); App::build(array( 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS) )); CakePlugin::load('TestPlugin'); $Text = new TextHelperTestObject($this->View, array('engine' => 'TestPlugin.TestPluginEngine')); $this->assertInstanceOf('TestPluginEngine', $Text->engine()); CakePlugin::unload('TestPlugin'); } /** * testAutoLink method * * @return void */ public function testAutoLink() { $text = 'The AWWWARD show happened today'; $result = $this->Text->autoLink($text); $this->assertEquals($text, $result); $text = 'This is a test text'; $expected = 'This is a test text'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'Text with a partial www.cakephp.org URL and test@cakephp.org email address'; $result = $this->Text->autoLink($text); $expected = 'Text with a partial www.cakephp.org URL and test@cakephp\.org email address'; $this->assertRegExp('#^' . $expected . '$#', $result); $text = 'Text with a partial link link'; $result = $this->Text->autoLink($text, array('escape' => false)); $this->assertEquals($text, $result); $text = 'This is a test text with URL http://www.cakephp.org'; $expected = 'This is a test text with URL http://www.cakephp.org'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL http://www.cakephp.org and some more text'; $expected = 'This is a test text with URL http://www.cakephp.org and some more text'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = "This is a test text with URL http://www.cakephp.org\tand some more text"; $expected = "This is a test text with URL http://www.cakephp.org\tand some more text"; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL http://www.cakephp.org(and some more text)'; $expected = 'This is a test text with URL http://www.cakephp.org(and some more text)'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL (http://www.cakephp.org/page/4) in brackets'; $expected = 'This is a test text with URL (http://www.cakephp.org/page/4) in brackets'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL [http://www.cakephp.org/page/4] in square brackets'; $expected = 'This is a test text with URL [http://www.cakephp.org/page/4] in square brackets'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL [http://www.example.com?aParam[]=value1&aParam[]=value2&aParam[]=value3] in square brackets'; $expected = 'This is a test text with URL [http://www.example.com?aParam[]=value1&aParam[]=value2&aParam[]=value3] in square brackets'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL ;http://www.cakephp.org/page/4; semi-colon'; $expected = 'This is a test text with URL ;http://www.cakephp.org/page/4; semi-colon'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL (http://www.cakephp.org/page/4/other(thing)) brackets'; $expected = 'This is a test text with URL (http://www.cakephp.org/page/4/other(thing)) brackets'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); } /** * Test mixing URLs and Email addresses in one confusing string. * * @return void */ public function testAutoLinkMixed() { $text = 'Text with a url/email http://example.com/store?email=mark@example.com and email.'; $expected = 'Text with a url/email ' . 'http://example.com/store?email=mark@example.com and email.'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); } /** * test autoLink() and options. * * @return void */ public function testAutoLinkOptions() { $text = 'This is a test text with URL http://www.cakephp.org'; $expected = 'This is a test text with URL http://www.cakephp.org'; $result = $this->Text->autoLink($text, array('class' => 'link')); $this->assertEquals($expected, $result); $text = 'This is a test text with URL http://www.cakephp.org'; $expected = 'This is a test text with URL http://www.cakephp.org'; $result = $this->Text->autoLink($text, array('class' => 'link', 'id' => 'MyLink')); $this->assertEquals($expected, $result); } /** * Test escaping for autoLink * * @return void */ public function testAutoLinkEscape() { $text = 'This is a test text with URL http://www.cakephp.org'; $expected = 'This is a <b>test</b> text with URL http://www.cakephp.org'; $result = $this->Text->autoLink($text); $this->assertEquals($expected, $result); $text = 'This is a test text with URL http://www.cakephp.org'; $expected = 'This is a test text with URL http://www.cakephp.org'; $result = $this->Text->autoLink($text, array('escape' => false)); $this->assertEquals($expected, $result); $text = 'test
    • lorem: http://example.org?some
    • ipsum: http://othersite.com/abc
    test'; $expected = 'test test'; $result = $this->Text->autoLink($text, array('escape' => false)); $this->assertEquals($expected, $result); } /** * Data provider for autoLinking * * @return array */ public static function autoLinkProvider() { return array( array( 'This is a test text', 'This is a test text', ), array( 'This is a test that includes (www.cakephp.org)', 'This is a test that includes (www.cakephp.org)', ), array( 'This is a test that includes www.cakephp.org:8080', 'This is a test that includes www.cakephp.org:8080', ), array( 'This is a test that includes http://de.wikipedia.org/wiki/Kanton_(Schweiz)#fragment', 'This is a test that includes http://de.wikipedia.org/wiki/Kanton_(Schweiz)#fragment', ), array( 'This is a test that includes www.wikipedia.org/wiki/Kanton_(Schweiz)#fragment', 'This is a test that includes www.wikipedia.org/wiki/Kanton_(Schweiz)#fragment', ), array( 'This is a test that includes http://example.com/test.php?foo=bar text', 'This is a test that includes http://example.com/test.php?foo=bar text', ), array( 'This is a test that includes www.example.com/test.php?foo=bar text', 'This is a test that includes www.example.com/test.php?foo=bar text', ), array( 'Text with a partial www.cakephp.org URL', 'Text with a partial www.cakephp.org URL', ), array( 'Text with a partial WWW.cakephp.org URL', 'Text with a partial WWW.cakephp.org URL', ), array( 'Text with a partial WWW.cakephp.org ©, URL', 'Text with a partial WWW.cakephp.org &copy, URL', ), array( 'Text with a url www.cot.ag/cuIb2Q and more', 'Text with a url www.cot.ag/cuIb2Q and more', ), array( 'Text with a url http://www.does--not--work.com and more', 'Text with a url http://www.does--not--work.com and more', ), array( 'Text with a url http://www.not--work.com and more', 'Text with a url http://www.not--work.com and more', ), array( 'Text with a url http://www.sub_domain.domain.pl and more', 'Text with a url http://www.sub_domain.domain.pl and more', ), array( 'Text with a partial www.kรผchenschรถhn-not-working.de URL', 'Text with a partial www.kรผchenschรถhn-not-working.de URL' ), array( 'Text with a partial http://www.kรผchenschรถhn-not-working.de URL', 'Text with a partial http://www.kรผchenschรถhn-not-working.de URL' ), ); } /** * testAutoLinkUrls method * * @dataProvider autoLinkProvider * @return void */ public function testAutoLinkUrls($text, $expected) { $result = $this->Text->autoLinkUrls($text); $this->assertEquals($expected, $result); } /** * Test the options for autoLinkUrls * * @return void */ public function testAutoLinkUrlsOptions() { $text = 'Text with a partial www.cakephp.org URL'; $expected = 'Text with a partial www.cakephp.org URL'; $result = $this->Text->autoLinkUrls($text, array('class' => 'link')); $this->assertRegExp('#^' . $expected . '$#', $result); $text = 'Text with a partial WWW.cakephp.org © URL'; $expected = 'Text with a partial WWW.cakephp.org © URL'; $result = $this->Text->autoLinkUrls($text, array('escape' => false)); $this->assertRegExp('#^' . $expected . '$#', $result); } /** * Test autoLinkUrls with the escape option. * * @return void */ public function testAutoLinkUrlsEscape() { $text = 'Text with a partial http://www.example.com link'; $expected = 'Text with a partial http://www.example.com link'; $result = $this->Text->autoLinkUrls($text, array('escape' => false)); $this->assertEquals($expected, $result); $text = 'Text with a partial www.example.com link'; $expected = 'Text with a partial www.example.com link'; $result = $this->Text->autoLinkUrls($text, array('escape' => false)); $this->assertEquals($expected, $result); $text = 'Text with a partial link link'; $expected = 'Text with a partial link link'; $result = $this->Text->autoLinkUrls($text, array('escape' => false)); $this->assertEquals($expected, $result); $text = 'Text with a partial
  • Path(); $files = array(); if ( $dir = opendir($eventPath) ) { while ( ($file = readdir($dir)) !== false ) { if ( is_file($eventPath.'/'.$file) ) { $files[$file] = $file; } } closedir($dir); } $exportFileList = array(); if ( $exportDetail ) { $file = 'zmEventDetail.html'; if ( !($fp = fopen( $eventPath.'/'.$file, 'w' )) ) { Fatal( "Can't open event detail export file '$file'" ); } fwrite( $fp, exportEventDetail( $event, $exportFrames, $exportImages ) ); fclose( $fp ); $exportFileList[$file] = $eventPath."/".$file; } if ( $exportFrames ) { $file = 'zmEventFrames.html'; if ( !($fp = fopen( $eventPath.'/'.$file, 'w' )) ) { Fatal( "Can't open event frames export file '$file'" ); } fwrite( $fp, exportEventFrames( $event, $exportDetail, $exportImages ) ); fclose( $fp ); $exportFileList[$file] = $eventPath."/".$file; } if ( $exportImages ) { $filesLeft = array(); $myfilelist = array(); foreach ( $files as $file ) { if ( preg_match( '/-(?:capture|analyse).jpg$/', $file ) ) { $exportFileList[$file] = $eventPath."/".$file; $myfilelist[$file] = $eventPath."/".$file; } else { $filesLeft[$file] = $file; } } $files = $filesLeft; // create an image slider if ( !empty($myfilelist) ) { $file = 'zmEventImages.html'; if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) Fatal( "Can't open event images export file '$file'" ); fwrite( $fp, exportEventImages( $event, $exportDetail, $exportFrames, $myfilelist ) ); fclose( $fp ); $exportFileList[$file] = $eventPath."/".$file; } } # end if exportImages if ( $exportVideo ) { $filesLeft = array(); foreach ( $files as $file ) { if ( preg_match( '/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file ) ) { $exportFileList[$file] = $eventPath.'/'.$file; } else { $filesLeft[$file] = $file; } } $files = $filesLeft; } # end if exportVideo if ( $exportMisc ) { foreach ( $files as $file ) { $exportFileList[$file] = $eventPath.'/'.$file; } $files = array(); } return array_values($exportFileList); } function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat, $exportStructure = false ) { if ( (!canView('Events')) || empty($eids) ) { return false; } $export_root = 'zmExport'; $export_listFile = 'zmFileList.txt'; $exportFileList = array(); $html_eventMaster = ''; if ( is_array($eids) ) { foreach ( $eids as $eid ) { $exportFileList = array_merge( $exportFileList, exportFileList( $eid , $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc ) ); } } else { $eid = $eids; $exportFileList = exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc ); } // create an master image slider if ( $exportImages ) { if ( !is_array($eids) ) { $eids = array($eids); } $monitorPath = ZM_DIR_EVENTS.'/'; $html_eventMaster = 'zmEventImagesMaster_'.date('Ymd_His'). '.html'; if ( !($fp = fopen( $monitorPath.'/'.$html_eventMaster, 'w' )) ) Fatal( "Can't open event images export file '$html_eventMaster'" ); fwrite($fp, exportEventImagesMaster($eids)); fclose($fp); $exportFileList[] = $monitorPath.'/'.$html_eventMaster; } if ( ! file_exists(ZM_DIR_EXPORTS) ) { if ( ! mkdir(ZM_DIR_EXPORTS) ) { Fatal("Can't create exports dir at '".ZM_DIR_EXPORTS."'"); } } $listFile = ZM_DIR_EXPORTS.'/'.$export_listFile; if ( !($fp = fopen($listFile, 'w')) ) { Fatal( "Can't open event export list file '$listFile'" ); } foreach ( $exportFileList as $exportFile ) { fwrite( $fp, "$exportFile\n" ); } fclose( $fp ); $archive = ''; if ( $exportFormat == 'tar' ) { $archive = ZM_DIR_EXPORTS.'/'.$export_root.'.tar.gz'; @unlink( $archive ); if ( $exportStructure == 'flat' ) { //strip file paths if we choose $command = "nice -10 tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile)." --xform='s#^.+/##x'"; } else { $command = "nice -10 tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile); } exec( $command, $output, $status ); if ( $status ) { Error( "Command '$command' returned with status $status" ); if ( $output[0] ) Error( "First line of output is '".$output[0]."'" ); return( false ); } } elseif ( $exportFormat == 'zip' ) { $archive = ZM_DIR_EXPORTS.'/'.$export_root.'.zip'; @unlink( $archive ); if ($exportStructure == 'flat') { $command = "cat ".escapeshellarg($listFile)." | nice -10 zip -q -j ".escapeshellarg($archive)." -@"; } else { $command = "cat ".escapeshellarg($listFile)." | nice -10 zip -q ".escapeshellarg($archive)." -@"; } //cat zmFileList.txt | zip -q zm_export.zip -@ //-bash: zip: command not found exec( $command, $output, $status ); if ( $status ) { Error("Command '$command' returned with status $status"); if ( $output[0] ) Error("First line of output is '".$output[0]."'"); return false; } } //clean up temporary files if ( !empty($html_eventMaster) ) { unlink($monitorPath.'/'.$html_eventMaster); } return '?view=archive%26type='.$exportFormat; } ZoneMinder-1.32.2/web/skins/classic/includes/control_functions.php0000644000000000000000000003144713365153156024004 0ustar rootrootCanZoom() ) { if ( $monitor->CanZoomCon() ) $cmds['ZoomRoot'] = "zoomCon"; elseif ( $monitor->CanZoomRel() ) $cmds['ZoomRoot'] = "zoomRel"; elseif ( $monitor->CanZoomAbs() ) $cmds['ZoomRoot'] = "zoomAbs"; $cmds['ZoomTele'] = $cmds['ZoomRoot']."Tele"; $cmds['ZoomWide'] = $cmds['ZoomRoot']."Wide"; $cmds['ZoomStop'] = "zoomStop"; $cmds['ZoomAuto'] = "zoomAuto"; $cmds['ZoomMan'] = "zoomMan"; } if ( $monitor->CanFocus() ) { if ( $monitor->CanFocusCon() ) $cmds['FocusRoot'] = "focusCon"; elseif ( $monitor->CanFocusRel() ) $cmds['FocusRoot'] = "focusRel"; elseif ( $monitor->CanFocusAbs() ) $cmds['FocusRoot'] = "focusAbs"; $cmds['FocusFar'] = $cmds['FocusRoot']."Far"; $cmds['FocusNear'] = $cmds['FocusRoot']."Near"; $cmds['FocusStop'] = "focusStop"; $cmds['FocusAuto'] = "focusAuto"; $cmds['FocusMan'] = "focusMan"; } if ( $monitor->CanIris() ) { if ( $monitor->CanIrisCon() ) $cmds['IrisRoot'] = "irisCon"; elseif ( $monitor->CanIrisRel() ) $cmds['IrisRoot'] = "irisRel"; elseif ( $monitor->CanIrisAbs() ) $cmds['IrisRoot'] = "irisAbs"; $cmds['IrisOpen'] = $cmds['IrisRoot']."Open"; $cmds['IrisClose'] = $cmds['IrisRoot']."Close"; $cmds['IrisStop'] = "irisStop"; $cmds['IrisAuto'] = "irisAuto"; $cmds['IrisMan'] = "irisMan"; } if ( $monitor->CanWhite() ) { if ( $monitor->CanWhiteCon() ) $cmds['WhiteRoot'] = "whiteCon"; elseif ( $monitor->CanWhiteRel() ) $cmds['WhiteRoot'] = "whiteRel"; elseif ( $monitor->CanWhiteAbs() ) $cmds['WhiteRoot'] = "whiteAbs"; $cmds['WhiteIn'] = $cmds['WhiteRoot']."In"; $cmds['WhiteOut'] = $cmds['WhiteRoot']."Out"; $cmds['WhiteAuto'] = "whiteAuto"; $cmds['WhiteMan'] = "whiteMan"; } if ( $monitor->CanGain() ) { if ( $monitor->CanGainCon() ) $cmds['GainRoot'] = "gainCon"; elseif ( $monitor->CanGainRel() ) $cmds['GainRoot'] = "gainRel"; elseif ( $monitor->CanGainAbs() ) $cmds['GainRoot'] = "gainAbs"; $cmds['GainUp'] = $cmds['GainRoot']."Up"; $cmds['GainDown'] = $cmds['GainRoot']."Down"; $cmds['GainAuto'] = "gainAuto"; $cmds['GainMan'] = "gainMan"; } if ( $monitor->CanMove() ) { if ( $monitor->CanMoveCon() ) { $cmds['MoveRoot'] = "moveCon"; $cmds['Center'] = "moveStop"; } elseif ( $monitor->CanMoveRel() ) { $cmds['MoveRoot'] = "moveRel"; $cmds['Center'] = $cmds['PresetHome']; } elseif ( $monitor->CanMoveAbs() ) { $cmds['MoveRoot'] = "moveAbs"; $cmds['Center'] = $cmds['PresetHome']; } else { $cmds['MoveRoot'] = ''; } $cmds['MoveUp'] = $cmds['MoveRoot']."Up"; $cmds['MoveDown'] = $cmds['MoveRoot']."Down"; $cmds['MoveLeft'] = $cmds['MoveRoot']."Left"; $cmds['MoveRight'] = $cmds['MoveRoot']."Right"; $cmds['MoveUpLeft'] = $cmds['MoveRoot']."UpLeft"; $cmds['MoveUpRight'] = $cmds['MoveRoot']."UpRight"; $cmds['MoveDownLeft'] = $cmds['MoveRoot']."DownLeft"; $cmds['MoveDownRight'] = $cmds['MoveRoot']."DownRight"; } return( $cmds ); } function controlFocus( $monitor, $cmds ) { ob_start(); ?>
    CanFocusCon() ) { ?> onclick="controlCmd('')">
    CanAutoFocus() ) { ?>
    CanZoomCon() ) { ?> onclick="controlCmd('')">
    CanAutoZoom() ) { ?>
    CanIrisCon() ) { ?> onclick="controlCmd('')">
    CanAutoIris() ) { ?>
    CanWhiteCon() ) { ?> onclick="controlCmd('')">
    CanAutoWhite() ) { ?>
    CanPan(); $hasTilt = $monitor->CanTilt(); $hasDiag = $hasPan && $hasTilt && $monitor->CanMoveDiag(); ?>
    Id() ) ) as $row ) { $labels[$row['Preset']] = $row['Label']; } $presetBreak = (int)(($monitor->NumPresets()+1)/((int)(($monitor->NumPresets()-1)/MAX_PRESETS)+1)); ob_start(); ?>
    NumPresets(); $i++ ) { ?> " value="" onclick="controlCmd('');"/>
    HasHomePreset() ) { ?> CanSetPresets() ) { ?>
    CanWake() ) { ?> CanSleep() ) { ?> CanReset() ) { ?>
    CanFocus() ) echo controlFocus($monitor, $cmds); if ( $monitor->CanZoom() ) echo controlZoom($monitor, $cmds); if ( $monitor->CanIris() ) echo controlIris($monitor, $cmds); if ( $monitor->CanWhite() ) echo controlWhite($monitor, $cmds); if ( $monitor->CanMove() ) { ?>
    CanWake() || $monitor->CanSleep() || $monitor->CanReset() ) echo controlPower($monitor, $cmds); if ( $monitor->HasPresets() ) echo controlPresets($monitor, $cmds); ?>
    ZoneMinder-1.32.2/web/skins/classic/includes/timeline_functions.php0000644000000000000000000003547313365153156024135 0ustar rootroot"; if ( $scaleRange >= $minLines ) { $scale['range'] = $scaleRange; break; } } if ( !isset($scale['range']) ) { $scale['range'] = (int)($range/($scale['factor']*$align)); } $scale['divisor'] = 1; while ( ($scale['range']/$scale['divisor']) > $maxLines ) { $scale['divisor']++; } $scale['lines'] = (int)($scale['range']/$scale['divisor']); return( $scale ); } function getYScale( $range, $minLines, $maxLines ) { $scale['range'] = $range; $scale['divisor'] = 1; while ( $scale['range']/$scale['divisor'] > $maxLines ) { $scale['divisor']++; } $scale['lines'] = (int)(($scale['range']-1)/$scale['divisor'])+1; return( $scale ); } function getSlotFrame( $slot ) { $slotFrame = isset($slot['frame'])?$slot['frame']['FrameId']:1; if ( false && $slotFrame ) { $slotFrame -= $monitor['PreEventCount']; if ( $slotFrame < 1 ) $slotFrame = 1; } return( $slotFrame ); } function parseFilterToTree( $filter ) { if ( count($filter['terms']) <= 0 ) { return false; } $terms = $filter['terms']; $StorageArea = NULL; $postfixExpr = array(); $postfixStack = array(); $priorities = array( '<' => 1, '<=' => 1, '>' => 1, '>=' => 1, '=' => 2, '!=' => 2, '=~' => 2, '!~' => 2, '=[]' => 2, '![]' => 2, 'and' => 3, 'or' => 4, ); for ( $i = 0; $i <= count($terms); $i++ ) { if ( !empty($terms[$i]['cnj']) ) { while( true ) { if ( !count($postfixStack) ) { $postfixStack[] = array('type'=>'cnj', 'value'=>$terms[$i]['cnj'], 'sqlValue'=>$terms[$i]['cnj']); break; } elseif ( $postfixStack[count($postfixStack)-1]['type'] == 'obr' ) { $postfixStack[] = array('type'=>'cnj', 'value'=>$terms[$i]['cnj'], 'sqlValue'=>$terms[$i]['cnj']); break; } elseif ( $priorities[$terms[$i]['cnj']] < $priorities[$postfixStack[count($postfixStack)-1]['value']] ) { $postfixStack[] = array('type'=>'cnj', 'value'=>$terms[$i]['cnj'], 'sqlValue'=>$terms[$i]['cnj']); break; } else { $postfixExpr[] = array_pop($postfixStack); } } } if ( !empty($terms[$i]['obr']) ) { for ( $j = 0; $j < $terms[$i]['obr']; $j++ ) { $postfixStack[] = array('type'=>'obr', 'value'=>$terms[$i]['obr']); } } if ( !empty($terms[$i]['attr']) ) { $dtAttr = false; switch ( $terms[$i]['attr']) { case 'MonitorName': $sqlValue = 'M.'.preg_replace( '/^Monitor/', '', $terms[$i]['attr']); break; case 'ServerId': $sqlValue .= 'M.ServerId'; break; case 'DateTime': case 'StartDateTime': $sqlValue = "E.StartTime"; $dtAttr = true; break; case 'Date': case 'StartDate': $sqlValue = "to_days( E.StartTime )"; $dtAttr = true; break; case 'Time': case 'StartTime': $sqlValue = "extract( hour_second from E.StartTime )"; break; case 'Weekday': case 'StartWeekday': $sqlValue = "weekday( E.StartTime )"; break; case 'EndDateTime': $sqlValue = "E.EndTime"; $dtAttr = true; break; case 'EndDate': $sqlValue = "to_days( E.EndTime )"; $dtAttr = true; break; case 'EndTime': $sqlValue = "extract( hour_second from E.EndTime )"; break; case 'EndWeekday': $sqlValue = "weekday( E.EndTime )"; break; case 'Id': case 'Name': case 'MonitorId': case 'StorageId': case 'Length': case 'Frames': case 'AlarmFrames': case 'TotScore': case 'AvgScore': case 'MaxScore': case 'Cause': case 'Notes': case 'StateId': case 'Archived': $sqlValue = "E.".$terms[$i]['attr']; break; case 'DiskPercent': // Need to specify a storage area, so need to look through other terms looking for a storage area, else we default to ZM_EVENTS_PATH if ( ! $StorageArea ) { for ( $j = 0; $j < count($terms); $j++ ) { if ( isset($terms[$j]['attr']) and $terms[$j]['attr'] == 'StorageId' and isset($terms[$j]['val']) ) { $StorageArea = new Storage($terms[$j]['val']); break; } } // end foreach remaining term if ( ! $StorageArea ) $StorageArea = new Storage(); } // end no StorageArea found yet $sqlValue = getDiskPercent($StorageArea); break; case 'DiskBlocks': // Need to specify a storage area, so need to look through other terms looking for a storage area, else we default to ZM_EVENTS_PATH if ( ! $StorageArea ) { for ( $j = 0; $j < count($terms); $j++ ) { if ( isset($terms[$j]['attr']) and $terms[$j]['attr'] == 'StorageId' and isset($terms[$j]['val']) ) { $StorageArea = new Storage($terms[$j]['val']); break; } } // end foreach remaining term if ( ! $StorageArea ) $StorageArea = new Storage(); } // end no StorageArea found yet $sqlValue = getDiskBlocks($StorageArea); break; default : $sqlValue = $terms[$i]['attr']; break; } if ( $dtAttr ) { $postfixExpr[] = array('type'=>'attr', 'value'=>$terms[$i]['attr'], 'sqlValue'=>$sqlValue, 'dtAttr'=>true); } else { $postfixExpr[] = array('type'=>'attr', 'value'=>$terms[$i]['attr'], 'sqlValue'=>$sqlValue); } } # end if attr if ( isset($terms[$i]['op']) ) { if ( empty($terms[$i]['op']) ) { $terms[$i]['op'] = '='; } switch ( $terms[$i]['op']) { case '=' : case '!=' : case '>=' : case '>' : case '<' : case '<=' : $sqlValue = $terms[$i]['op']; break; case '=~' : $sqlValue = 'regexp'; break; case '!~' : $sqlValue = 'not regexp'; break; case '=[]' : $sqlValue = 'in ('; break; case '![]' : $sqlValue = 'not in ('; break; default : Error('Unknown operator in filter '. $terms[$i]['op']); } while( true ) { if ( !count($postfixStack) ) { $postfixStack[] = array('type'=>'op', 'value'=>$terms[$i]['op'], 'sqlValue'=>$sqlValue); break; } elseif ( $postfixStack[count($postfixStack)-1]['type'] == 'obr' ) { $postfixStack[] = array('type'=>'op', 'value'=>$terms[$i]['op'], 'sqlValue'=>$sqlValue); break; } elseif ( $priorities[$terms[$i]['op']] < $priorities[$postfixStack[count($postfixStack)-1]['value']] ) { $postfixStack[] = array('type'=>'op', 'value'=>$terms[$i]['op'], 'sqlValue'=>$sqlValue ); break; } else { $postfixExpr[] = array_pop($postfixStack); } } // end while } // end if operator if ( isset($terms[$i]['val']) ) { $valueList = array(); foreach ( preg_split('/["\'\s]*?,["\'\s]*?/', preg_replace('/^["\']+?(.+)["\']+?$/', '$1', $terms[$i]['val'])) as $value ) { switch ( $terms[$i]['attr'] ) { case 'MonitorName': case 'Name': case 'Cause': case 'Notes': $value = "'$value'"; break; case 'ServerId': if ( $value == 'ZM_SERVER_ID' ) { $value = ZM_SERVER_ID; } else if ( $value == 'NULL' ) { } else { $value = dbEscape($value); } break; case 'StorageId': $StorageArea = new Storage($value); if ( $value != 'NULL' ) $value = dbEscape($value); break; case 'DateTime': case 'EndDateTime': case 'StartDateTime': $value = "'".strftime(STRF_FMT_DATETIME_DB, strtotime($value))."'"; break; case 'Date': case 'EndDate': case 'StartDate': $value = "to_days('".strftime(STRF_FMT_DATETIME_DB, strtotime($value))."' )"; break; case 'Time': case 'EndTime': case 'StartTime': $value = "extract( hour_second from '".strftime(STRF_FMT_DATETIME_DB, strtotime($value))."' )"; break; case 'Weekday': case 'EndWeekday': case 'StartWeekday': $value = "weekday( '".strftime(STRF_FMT_DATETIME_DB, strtotime($value))."' )"; break; default : if ( $value != 'NULL' ) $value = dbEscape($value); } // end switch attribute $valueList[] = $value; } // end foreach value $postfixExpr[] = array('type'=>'val', 'value'=>$terms[$i]['val'], 'sqlValue'=>join(',', $valueList)); } // end if has val if ( !empty($terms[$i]['cbr']) ) { for ( $j = 0; $j < $terms[$i]['cbr']; $j++ ) { while ( count($postfixStack) ) { $element = array_pop($postfixStack); if ( $element['type'] == 'obr' ) { $postfixExpr[count($postfixExpr)-1]['bracket'] = true; break; } $postfixExpr[] = $element; } } } } while ( count($postfixStack) ) { $postfixExpr[] = array_pop($postfixStack); } $exprStack = array(); //foreach ( $postfixExpr as $element ) //{ //echo $element['value'].' '; //} //echo "
    "; foreach ( $postfixExpr as $element ) { if ( $element['type'] == 'attr' || $element['type'] == 'val' ) { $node = array('data'=>$element, 'count'=>0); $exprStack[] = $node; } elseif ( $element['type'] == 'op' || $element['type'] == 'cnj' ) { $right = array_pop($exprStack); $left = array_pop($exprStack); $node = array('data'=>$element, 'count'=>2+$left['count']+$right['count'], 'right'=>$right, 'left'=>$left); $exprStack[] = $node; } else { Fatal("Unexpected element type '".$element['type']."', value '".$element['value']."'"); } } if ( count($exprStack) != 1 ) { Fatal('Expression stack has '.count($exprStack).' elements'); } return array_pop($exprStack); } function _parseTreeToInfix($node) { $expression = ''; if ( isset($node) ) { if ( isset($node['left']) ) { if ( !empty($node['data']['bracket']) ) $expression .= '( '; $expression .= _parseTreeToInfix($node['left']); } $expression .= $node['data']['value'].' '; if ( isset($node['right']) ) { $expression .= _parseTreeToInfix($node['right']); if ( !empty($node['data']['bracket']) ) $expression .= ') '; } } return $expression; } function parseTreeToInfix($tree) { return _parseTreeToInfix($tree); } function _parseTreeToSQL( $node, $cbr=false ) { $expression = ''; if ( $node ) { if ( isset($node['left']) ) { if ( !empty($node['data']['bracket']) ) $expression .= '( '; $expression .= _parseTreeToSQL( $node['left'] ); } $inExpr = $node['data']['type'] == 'op' && ($node['data']['value'] == '=[]' || $node['data']['value'] == '![]'); $expression .= $node['data']['sqlValue']; if ( !$inExpr ) $expression .= ' '; if ( $cbr ) $expression .= ') '; if ( isset($node['right']) ) { $expression .= _parseTreeToSQL( $node['right'], $inExpr ); if ( !empty($node['data']['bracket']) ) $expression .= ') '; } } return( $expression ); } function parseTreeToSQL( $tree ) { return( _parseTreeToSQL( $tree ) ); } function _parseTreeToFilter( $node, &$terms, &$level ) { $elements = array(); if ( $node ) { if ( isset($node['left']) ) { if ( !empty($node['data']['bracket']) ) $terms[$level]['obr'] = 1; _parseTreeToFilter( $node['left'], $terms, $level ); } if ( $node['data']['type'] == 'cnj' ) { $level++; } $terms[$level][$node['data']['type']] = $node['data']['value']; if ( isset($node['right']) ) { _parseTreeToFilter( $node['right'], $terms, $level ); if ( !empty($node['data']['bracket']) ) $terms[$level]['cbr'] = 1; } } } function parseTreeToFilter( $tree ) { $terms = array(); if ( isset($tree) ) { $level = 0; _parseTreeToFilter( $tree, $terms, $level ); } return( array( 'Query' => array( 'terms' => $terms ) ) ); } function parseTreeToQuery( $tree ) { $filter = parseTreeToFilter( $tree ); parseFilter( $filter, false, '&' ); return( $filter['query'] ); } function _drawTree( $node, $level ) { if ( isset($node['left']) ) { _drawTree( $node['left'], $level+1 ); } echo str_repeat( ".", $level*2 ).$node['data']['value']."
    "; if ( isset($node['right']) ) { _drawTree( $node['right'], $level+1 ); } } function drawTree( $tree ) { _drawTree( $tree, 0 ); } function _extractDatetimeRange( &$node, &$minTime, &$maxTime, &$expandable, $subOr ) { $pruned = $leftPruned = $rightPruned = false; if ( $node ) { if ( isset($node['left']) && isset($node['right']) ) { if ( $node['data']['type'] == 'cnj' && $node['data']['value'] == 'or' ) { $subOr = true; } elseif ( !empty($node['left']['data']['dtAttr']) ) { if ( $subOr ) { $expandable = false; } elseif ( $node['data']['type'] == 'op' ) { if ( $node['data']['value'] == '>' || $node['data']['value'] == '>=' ) { if ( !$minTime || $minTime > $node['right']['data']['sqlValue'] ) { $minTime = $node['right']['data']['value']; return( true ); } } if ( $node['data']['value'] == '<' || $node['data']['value'] == '<=' ) { if ( !$maxTime || $maxTime < $node['right']['data']['sqlValue'] ) { $maxTime = $node['right']['data']['value']; return( true ); } } } else { Fatal( "Unexpected node type '".$node['data']['type']."'" ); } return( false ); } $leftPruned = _extractDatetimeRange( $node['left'], $minTime, $maxTime, $expandable, $subOr ); $rightPruned = _extractDatetimeRange( $node['right'], $minTime, $maxTime, $expandable, $subOr ); if ( $leftPruned && $rightPruned ) { $pruned = true; } elseif ( $leftPruned ) { $node = $node['right']; } elseif ( $rightPruned ) { $node = $node['left']; } } } return( $pruned ); } function extractDatetimeRange( &$tree, &$minTime, &$maxTime, &$expandable ) { $minTime = ""; $maxTime = ""; $expandable = true; _extractDateTimeRange( $tree, $minTime, $maxTime, $expandable, false ); } function appendDatetimeRange( &$tree, $minTime, $maxTime=false ) { $attrNode = array( 'data'=>array( 'type'=>'attr', 'value'=>'StartDateTime', 'sqlValue'=>'E.StartTime', 'dtAttr'=>true ), 'count'=>0 ); $valNode = array( 'data'=>array( 'type'=>'val', 'value'=>$minTime, 'sqlValue'=>$minTime ), 'count'=>0 ); $opNode = array( 'data'=>array( 'type'=>'op', 'value'=>'>=', 'sqlValue'=>'>=' ), 'count'=>2, 'left'=>$attrNode, 'right'=>$valNode ); if ( isset($tree) ) { $cnjNode = array( 'data'=>array( 'type'=>'cnj', 'value'=>'and', 'sqlValue'=>'and' ), 'count'=>2+$tree['count']+$opNode['count'], 'left'=>$tree, 'right'=>$opNode ); $tree = $cnjNode; } else { $tree = $opNode; } if ( $maxTime ) { $attrNode = array( 'data'=>array( 'type'=>'attr', 'value'=>'StartDateTime', 'sqlValue'=>'E.StartTime', 'dtAttr'=>true ), 'count'=>0 ); $valNode = array( 'data'=>array( 'type'=>'val', 'value'=>$maxTime, 'sqlValue'=>$maxTime ), 'count'=>0 ); $opNode = array( 'data'=>array( 'type'=>'op', 'value'=>'<=', 'sqlValue'=>'<=' ), 'count'=>2, 'left'=>$attrNode, 'right'=>$valNode ); $cnjNode = array( 'data'=>array( 'type'=>'cnj', 'value'=>'and', 'sqlValue'=>'and' ), 'count'=>2+$tree['count']+$opNode['count'], 'left'=>$tree, 'right'=>$opNode ); $tree = $cnjNode; } } ?> ZoneMinder-1.32.2/web/skins/classic/includes/functions.php0000644000000000000000000003445013365153156022241 0ustar rootroot'; } } return implode("\n", $html); } ?> <?php echo ZM_WEB_TITLE_PREFIX ?> - <?php echo validHtmlStr($title) ?> "; } else { echo ' '; } ?> ZoneMinder-1.32.2/web/CMakeLists.txt0000644000000000000000000000330413365153155015673 0ustar rootroot# CMakeLists.txt for the ZoneMinder web files # Process the api subdirectory add_subdirectory(api) # Process the tools/mootools subdirectory add_subdirectory(tools/mootools) # Create files from the .in files configure_file(includes/config.php.in "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" @ONLY) # Install the web files install(DIRECTORY api ajax css fonts graphics includes js lang skins tools views DESTINATION "${ZM_WEBDIR}" PATTERN "*.in" EXCLUDE PATTERN "*Make*" EXCLUDE PATTERN "*cmake*" EXCLUDE) install(FILES index.php robots.txt DESTINATION "${ZM_WEBDIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" DESTINATION "${ZM_WEBDIR}/includes") # Install the api config files (if its not in the source directory) if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/core.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/database.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/lib/Cake/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/lib/Cake") endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) # Install the mootools symlinks (if its not in the source directory) if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tools/mootools/mootools-core.js" DESTINATION "${ZM_WEBDIR}/tools/mootools") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tools/mootools/mootools-more.js" DESTINATION "${ZM_WEBDIR}/tools/mootools") endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) ZoneMinder-1.32.2/web/includes/0000755000000000000000000000000013365153156014742 5ustar rootrootZoneMinder-1.32.2/web/includes/lang.php0000644000000000000000000000562713365153156016406 0ustar rootroot $value) { if ( ! array_key_exists( $key, $SLANG ) ) $SLANG[$key] = $DLANG[$key]; } } // // Date and time formats fallback, if not set up by the language file already // defined("DATE_FMT_CONSOLE_LONG") or define("DATE_FMT_CONSOLE_LONG", "D jS M, g:ia"); // This is the main console date/time, date() or strftime() format defined("DATE_FMT_CONSOLE_SHORT") or define( "DATE_FMT_CONSOLE_SHORT", "%H:%M" ); // This is the xHTML console date/time, date() or strftime() format defined("STRF_FMT_DATETIME") or define( "STRF_FMT_DATETIME", "%c" ); // Strftime locale aware format for dates with times defined("STRF_FMT_DATE") or define( "STRF_FMT_DATE", "%x" ); // Strftime locale aware format for dates without times defined("STRF_FMT_TIME") or define( "STRF_FMT_TIME", "%X" ); // Strftime locale aware format for times without dates defined("STRF_FMT_DATETIME_SHORT") or define( "STRF_FMT_DATETIME_SHORT", "%y/%m/%d %H:%M:%S" ); // Strftime shorter format for dates with time, not locale aware defined("STRF_FMT_DATETIME_SHORTER") or define( "STRF_FMT_DATETIME_SHORTER", "%m/%d %H:%M:%S" );// Strftime shorter format for dates with time, not locale aware, used where space is tight ?> ZoneMinder-1.32.2/web/includes/Frame.php0000644000000000000000000000601313365153155016504 0ustar rootroot $v) { $this->{$k} = $v; } } else { Error("No row for Frame " . $IdOrRow ); } } # end if isset($IdOrRow) } // end function __construct public function Storage() { return $this->Event()->Storage(); } public function Event() { return new Event( $this->{'EventId'} ); } public function __call( $fn, array $args){ if ( count( $args ) ) { $this->{$fn} = $args[0]; } if ( array_key_exists( $fn, $this ) ) { return $this->{$fn}; $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning( "Unknown function call Frame->$fn from $file:$line" ); } } public function getImageSrc( $show='capture' ) { return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'FrameId'}.'&eid='.$this->{'EventId'}.'&show='.$show; #return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg'; } // end function getImageSrc public static function find( $parameters = array(), $options = NULL ) { $sql = 'SELECT * FROM Frames'; $values = array(); if ( sizeof($parameters) ) { $sql .= ' WHERE ' . implode( ' AND ', array_map( function($v){ return $v.'=?'; }, array_keys( $parameters ) ) ); $values = array_values( $parameters ); } if ( $options ) { if ( isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } if ( isset($options['limit']) ) { if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { $sql .= ' LIMIT ' . $options['limit']; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Invalid value for limit(".$options['limit'].") passed to Frame::find from $file:$line"); return array(); } } } $results = dbFetchAll($sql, NULL, $values); if ( $results ) { return array_map( function($id){ return new Frame($id); }, $results ); } return array(); } public static function find_one( $parameters = array(), $options = null ) { $options['limit'] = 1; $results = Frame::find($parameters, $options); if ( ! sizeof($results) ) { return; } return $results[0]; } } # end class ?> ZoneMinder-1.32.2/web/includes/Filter.php0000644000000000000000000001305513365153155016703 0ustar rootroot null, 'Name' => '', 'AutoExecute' => 0, 'AutoExecuteCmd' => 0, 'AutoEmail' => 0, 'AutoDelete' => 0, 'AutoArchive' => 0, 'AutoVideo' => 0, 'AutoUpload' => 0, 'AutoMessage' => 0, 'AutoMove' => 0, 'AutoMoveTo' => 0, 'UpdateDiskSpace' => 0, 'Background' => 0, 'Concurrent' => 0, 'limit' => 100, 'Query' => array(), 'sort_field' => ZM_WEB_EVENT_SORT_FIELD, 'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, ); public function __construct( $IdOrRow=NULL ) { $row = NULL; if ( $IdOrRow ) { if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { $row = dbFetchOne('SELECT * FROM Filters WHERE Id=?', NULL, array($IdOrRow)); if ( ! $row ) { Error('Unable to load Filter record for Id=' . $IdOrRow); } } elseif ( is_array($IdOrRow) ) { $row = $IdOrRow; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Unknown argument passed to Filter Constructor from $file:$line)"); Error("Unknown argument passed to Filter Constructor ($IdOrRow)"); return; } } # end if isset($IdOrRow) if ( $row ) { foreach ($row as $k => $v) { $this->{$k} = $v; } if ( array_key_exists('Query', $this) and $this->{'Query'} ) { $this->{'Query'} = jsonDecode($this->{'Query'}); } else { $this->{'Query'} = array(); } } } // end function __construct public function __call( $fn, array $args ) { if ( count( $args ) ) { $this->{$fn} = $args[0]; } if ( array_key_exists( $fn, $this ) ) { return $this->{$fn}; } else if ( array_key_exists( $fn, $this->defaults ) ) { $this->{$fn} = $this->defaults{$fn}; return $this->{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning( "Unknown function call Filter->$fn from $file:$line" ); } } public function terms( ) { if ( func_num_args( ) ) { $this->Query()['terms'] = func_get_arg(0); } if ( isset( $this->Query()['terms'] ) ) { return $this->Query()['terms']; } return array(); } // The following three fields are actually stored in the Query public function sort_field( ) { if ( func_num_args( ) ) { $this->Query()['sort_field'] = func_get_arg(0); } if ( isset( $this->Query()['sort_field'] ) ) { return $this->{'Query'}['sort_field']; } return $this->defaults{'sort_field'}; } public function sort_asc( ) { if ( func_num_args( ) ) { $this->{'Query'}['sort_asc'] = func_get_arg(0); } if ( isset( $this->Query()['sort_asc'] ) ) { return $this->{'Query'}['sort_asc']; } return $this->defaults{'sort_asc'}; } public function limit( ) { if ( func_num_args( ) ) { $this->{'Query'}['limit'] = func_get_arg(0); } if ( isset( $this->Query()['limit'] ) ) return $this->{'Query'}['limit']; return $this->defaults{'limit'}; } public static function find( $parameters = null, $options = null ) { $filters = array(); $sql = 'SELECT * FROM Filters '; $values = array(); if ( $parameters ) { $fields = array(); $sql .= 'WHERE '; foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; } else if ( is_array( $value ) ) { $func = function(){return '?';}; $fields[] = $field.' IN ('.implode(',', array_map($func, $value)). ')'; $values += $value; } else { $fields[] = $field.'=?'; $values[] = $value; } } $sql .= implode(' AND ', $fields); } if ( $options ) { if ( isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } if ( isset($options['limit']) ) { if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { $sql .= ' LIMIT ' . $options['limit']; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Invalid value for limit(".$options['limit'].") passed to Filter::find from $file:$line"); return array(); } } } $result = dbQuery($sql, $values); $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Filter'); foreach ( $results as $row => $obj ) { $filters[] = $obj; } return $filters; } # end find() public static function find_one( $parameters = array() ) { $results = Filter::find($parameters, array('limit'=>1)); if ( ! sizeof($results) ) { return; } return $results[0]; } # end find_one() public function delete() { dbQuery('DELETE FROM Filters WHERE Id = ?', array($this->{'Id'})); } # end function delete() public function set( $data ) { foreach ($data as $k => $v) { if ( is_array( $v ) ) { $this->{$k} = $v; } else if ( is_string( $v ) ) { $this->{$k} = trim( $v ); } else if ( is_integer( $v ) ) { $this->{$k} = $v; } else if ( is_bool( $v ) ) { $this->{$k} = $v; } else { Error( "Unknown type $k => $v of var " . gettype( $v ) ); $this->{$k} = $v; } } } # end function set } # end class Filter ?> ZoneMinder-1.32.2/web/includes/control_functions.php0000644000000000000000000007004413365153155021227 0ustar rootrootHasFocusSpeed() ) { $speed = intval(round($monitor->MinFocusSpeed()+(($monitor->MaxFocusSpeed()-$monitor->MinFocusSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : { $step = intval(round($monitor->MinFocusStep()+(($monitor->MaxFocusStep()-$monitor->MinFocusStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; } case 'Con' : { if ( $monitor->AutoStopTimeout() ) { $slowSpeed = intval(round($monitor->MinFocusSpeed()+(($monitor->MaxFocusSpeed()-$monitor->MinFocusSpeed())*$slow))); if ( $speed < $slowSpeed ) { $ctrlCommand .= ' --autostop'; } } break; } } break; } case 'zoom' : $factor = $_REQUEST['yge']/100; if ( $monitor->HasZoomSpeed() ) { $speed = intval(round($monitor->MinZoomSpeed()+(($monitor->MaxZoomSpeed()-$monitor->MinZoomSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinZoomStep()+(($monitor->MaxZoomStep()-$monitor->MinZoomStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; case 'Con' : if ( $monitor->AutoStopTimeout() ) { $slowSpeed = intval(round($monitor->MinZoomSpeed()+(($monitor->MaxZoomSpeed()-$monitor->MinZoomSpeed())*$slow))); if ( $speed < $slowSpeed ) { $ctrlCommand .= ' --autostop'; } } break; } break; case 'iris' : $factor = $_REQUEST['yge']/100; if ( $monitor->HasIrisSpeed() ) { $speed = intval(round($monitor->MinIrisSpeed()+(($monitor->MaxIrisSpeed()-$monitor->MinIrisSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinIrisStep()+(($monitor->MaxIrisStep()-$monitor->MinIrisStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; } break; case 'white' : $factor = $_REQUEST['yge']/100; if ( $monitor->HasWhiteSpeed() ) { $speed = intval(round($monitor->MinWhiteSpeed()+(($monitor->MaxWhiteSpeed()-$monitor->MinWhiteSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinWhiteStep()+(($monitor->MaxWhiteStep()-$monitor->MinWhiteStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; } break; case 'gain' : $factor = $_REQUEST['yge']/100; if ( $monitor->HasGainSpeed() ) { $speed = intval(round($monitor->MinGainSpeed()+(($monitor->MaxGainSpeed()-$monitor->MinGainSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinGainStep()+(($monitor->MaxGainStep()-$monitor->MinGainStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; } break; case 'move' : $xFactor = empty($_REQUEST['xge'])?0:$_REQUEST['xge']/100; $yFactor = empty($_REQUEST['yge'])?0:$_REQUEST['yge']/100; if ( $monitor->Orientation() != '0' ) { $conversions = array( '90' => array( 'Up' => 'Left', 'Down' => 'Right', 'Left' => 'Down', 'Right' => 'Up', 'UpLeft' => 'DownLeft', 'UpRight' => 'UpLeft', 'DownLeft' => 'DownRight', 'DownRight' => 'UpRight', ), '180' => array( 'Up' => 'Down', 'Down' => 'Up', 'Left' => 'Right', 'Right' => 'Left', 'UpLeft' => 'DownRight', 'UpRight' => 'DownLeft', 'DownLeft' => 'UpRight', 'DownRight' => 'UpLeft', ), '270' => array( 'Up' => 'Right', 'Down' => 'Left', 'Left' => 'Up', 'Right' => 'Down', 'UpLeft' => 'UpRight', 'UpRight' => 'DownRight', 'DownLeft' => 'UpLeft', 'DownRight' => 'DownLeft', ), 'hori' => array( 'Up' => 'Up', 'Down' => 'Down', 'Left' => 'Right', 'Right' => 'Left', 'UpLeft' => 'UpRight', 'UpRight' => 'UpLeft', 'DownLeft' => 'DownRight', 'DownRight' => 'DownLeft', ), 'vert' => array( 'Up' => 'Down', 'Down' => 'Up', 'Left' => 'Left', 'Right' => 'Right', 'UpLeft' => 'DownLeft', 'UpRight' => 'DownRight', 'DownLeft' => 'UpLeft', 'DownRight' => 'UpRight', ), ); $new_dirn = $conversions[$monitor->Orientation()][$dirn]; $_REQUEST['control'] = preg_replace( "/_$dirn\$/", "_$new_dirn", $_REQUEST['control'] ); $dirn = $new_dirn; } if ( $monitor->HasPanSpeed() && $xFactor ) { if ( $monitor->HasTurboPan() ) { if ( $xFactor >= $turbo ) { $panSpeed = $monitor->TurboPanSpeed(); } else { $xFactor = $xFactor/$turbo; $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } } else { $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } $ctrlCommand .= ' --panspeed='.$panSpeed; } if ( $monitor->HasTiltSpeed() && $yFactor ) { if ( $monitor->HasTurboTilt() ) { if ( $yFactor >= $turbo ) { $tiltSpeed = $monitor->TurboTiltSpeed(); } else { $yFactor = $yFactor/$turbo; $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } } else { $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } $ctrlCommand .= ' --tiltspeed='.$tiltSpeed; } switch( $mode ) { case 'Rel' : case 'Abs' : if ( preg_match( '/(Left|Right)$/', $dirn ) ) { $panStep = intval(round($monitor->MinPanStep()+(($monitor->MaxPanStep()-$monitor->MinPanStep())*$xFactor))); $ctrlCommand .= ' --panstep='.$panStep; } if ( preg_match( '/^(Up|Down)/', $dirn ) ) { $tiltStep = intval(round($monitor->MinTiltStep()+(($monitor->MaxTiltStep()-$monitor->MinTiltStep())*$yFactor))); $ctrlCommand .= ' --tiltstep='.$tiltStep; } break; case 'Con' : if ( $monitor->AutoStopTimeout() ) { $slowPanSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$slow))); $slowTiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$slow))); if ( (!isset($panSpeed) || ($panSpeed < $slowPanSpeed)) && (!isset($tiltSpeed) || ($tiltSpeed < $slowTiltSpeed)) ) { $ctrlCommand .= ' --autostop'; } } break; } } } else { Error('Invalid control parameter: ' . $_REQUEST['control'] ); } } elseif ( isset($_REQUEST['x']) && isset($_REQUEST['y']) ) { if ( $_REQUEST['control'] == 'moveMap' ) { $x = deScale( $_REQUEST['x'], $_REQUEST['scale'] ); $y = deScale( $_REQUEST['y'], $_REQUEST['scale'] ); switch ( $monitor->Orientation() ) { case '0' : case '180' : case 'hori' : case 'vert' : $width = $monitor->Width(); $height = $monitor->Height(); break; case '90' : case '270' : $width = $monitor->Height(); $height = $monitor->Width(); break; } switch ( $monitor->Orientation() ) { case '90' : $tempY = $y; $y = $height - $x; $x = $tempY; break; case '180' : $x = $width - $x; $y = $height - $y; break; case '270' : $tempX = $x; $x = $width - $y; $y = $tempX; break; case 'hori' : $x = $width - $x; break; case 'vert' : $y = $height - $y; break; } //$ctrlCommand .= " --xcoord=$x --ycoord=$y --width=$width --height=$height"; $ctrlCommand .= " --xcoord=$x --ycoord=$y"; } elseif ( $_REQUEST['control'] == 'movePseudoMap' ) { $x = deScale( $_REQUEST['x'], $_REQUEST['scale'] ); $y = deScale( $_REQUEST['y'], $_REQUEST['scale'] ); $halfWidth = $monitor->Width() / 2; $halfHeight = $monitor->Height() / 2; $xFactor = ($x - $halfWidth)/$halfWidth; $yFactor = ($y - $halfHeight)/$halfHeight; switch ( $monitor->Orientation() ) { case '90' : $tempYFactor = $y; $yFactor = -$xFactor; $xFactor = $tempYFactor; break; case '180' : $xFactor = -$xFactor; $yFactor = -$yFactor; break; case '270' : $tempXFactor = $x; $xFactor = -$yFactor; $yFactor = $tempXFactor; break; case 'hori' : $xFactor = -$xFactor; break; case 'vert' : $yFactor = -$yFactor; break; } $turbo = 0.9; // Threshold for turbo speed $blind = 0.1; // Threshold for blind spot $panControl = ''; $tiltControl = ''; if ( $xFactor > $blind ) { $panControl = 'Right'; } elseif ( $xFactor < -$blind ) { $panControl = 'Left'; } if ( $yFactor > $blind ) { $tiltControl = 'Down'; } elseif ( $yFactor < -$blind ) { $tiltControl = 'Up'; } $dirn = $tiltControl.$panControl; if ( !$dirn ) { // No command, probably in blind spot in middle $_REQUEST['control'] = 'null'; return( false ); } else { $_REQUEST['control'] = 'moveRel'.$dirn; $xFactor = abs($xFactor); $yFactor = abs($yFactor); if ( $monitor->HasPanSpeed() && $xFactor ) { if ( $monitor->HasTurboPan() ) { if ( $xFactor >= $turbo ) { $panSpeed = $monitor->TurboPanSpeed(); } else { $xFactor = $xFactor/$turbo; $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } } else { $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } } if ( $monitor->HasTiltSpeed() && $yFactor ) { if ( $monitor->HasTurboTilt() ) { if ( $yFactor >= $turbo ) { $tiltSpeed = $monitor->TurboTiltSpeed(); } else { $yFactor = $yFactor/$turbo; $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } } else { $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } } if ( preg_match( '/(Left|Right)$/', $dirn ) ) { $panStep = intval(round($monitor->MinPanStep()+(($monitor->MaxPanStep()-$monitor->MinPanStep())*$xFactor))); $ctrlCommand .= ' --panstep='.$panStep.' --panspeed='.$panSpeed; } if ( preg_match( '/^(Up|Down)/', $dirn ) ) { $tiltStep = intval(round($monitor->MinTiltStep()+(($monitor->MaxTiltStep()-$monitor->MinTiltStep())*$yFactor))); $ctrlCommand .= ' --tiltstep='.$tiltStep.' --tiltspeed='.$tiltSpeed; } } } elseif ( $_REQUEST['control'] == 'moveConMap' ) { $x = deScale( $_REQUEST['x'], $_REQUEST['scale'] ); $y = deScale( $_REQUEST['y'], $_REQUEST['scale'] ); $halfWidth = $monitor->Width() / 2; $halfHeight = $monitor->Height() / 2; $xFactor = ($x - $halfWidth)/$halfWidth; $yFactor = ($y - $halfHeight)/$halfHeight; switch ( $monitor->Orientation() ) { case '90' : $tempYFactor = $y; $yFactor = -$xFactor; $xFactor = $tempYFactor; break; case '180' : $xFactor = -$xFactor; $yFactor = -$yFactor; break; case '270' : $tempXFactor = $x; $xFactor = -$yFactor; $yFactor = $tempXFactor; break; case 'hori' : $xFactor = -$xFactor; break; case 'vert' : $yFactor = -$yFactor; break; } $slow = 0.9; // Threshold for slow speed/timeouts $turbo = 0.9; // Threshold for turbo speed $blind = 0.1; // Threshold for blind spot $panControl = ''; $tiltControl = ''; if ( $xFactor > $blind ) { $panControl = 'Right'; } elseif ( $xFactor < -$blind ) { $panControl = 'Left'; } if ( $yFactor > $blind ) { $tiltControl = 'Down'; } elseif ( $yFactor < -$blind ) { $tiltControl = 'Up'; } $dirn = $tiltControl.$panControl; if ( !$dirn ) { // No command, probably in blind spot in middle $_REQUEST['control'] = 'moveStop'; } else { $_REQUEST['control'] = 'moveCon'.$dirn; $xFactor = abs($xFactor); $yFactor = abs($yFactor); if ( $monitor->HasPanSpeed() && $xFactor ) { if ( $monitor->HasTurboPan() ) { if ( $xFactor >= $turbo ) { $panSpeed = $monitor->TurboPanSpeed(); } else { $xFactor = $xFactor/$turbo; $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } } else { $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } } if ( $monitor->HasTiltSpeed() && $yFactor ) { if ( $monitor->HasTurboTilt() ) { if ( $yFactor >= $turbo ) { $tiltSpeed = $monitor->TurboTiltSpeed(); } else { $yFactor = $yFactor/$turbo; $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } } else { $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } } if ( preg_match( '/(Left|Right)$/', $dirn ) ) { $ctrlCommand .= ' --panspeed='.$panSpeed; } if ( preg_match( '/^(Up|Down)/', $dirn ) ) { $ctrlCommand .= ' --tiltspeed='.$tiltSpeed; } if ( $monitor->AutoStopTimeout() ) { $slowPanSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$slow))); $slowTiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$slow))); if ( (!isset($panSpeed) || ($panSpeed < $slowPanSpeed)) && (!isset($tiltSpeed) || ($tiltSpeed < $slowTiltSpeed)) ) { $ctrlCommand .= ' --autostop'; } } } } else { $slow = 0.9; // Threshold for slow speed/timeouts $turbo = 0.9; // Threshold for turbo speed $long_y = 48; $short_x = 32; $short_y = 32; if ( preg_match( '/^([a-z]+)([A-Z][a-z]+)([A-Z][a-z]+)$/', $_REQUEST['control'], $matches ) ) { $command = $matches[1]; $mode = $matches[2]; $dirn = $matches[3]; switch( $command ) { case 'focus' : switch( $dirn ) { case 'Near' : $factor = ($long_y-($y+1))/$long_y; break; case 'Far' : $factor = ($y+1)/$long_y; break; } if ( $monitor->HasFocusSpeed() ) { $speed = intval(round($monitor->MinFocusSpeed()+(($monitor->MaxFocusSpeed()-$monitor->MinFocusSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinFocusStep()+(($monitor->MaxFocusStep()-$monitor->MinFocusStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; case 'Con' : if ( $monitor->AutoStopTimeout() ) { $slowSpeed = intval(round($monitor->MinFocusSpeed()+(($monitor->MaxFocusSpeed()-$monitor->MinFocusSpeed())*$slow))); if ( $speed < $slowSpeed ) { $ctrlCommand .= ' --autostop'; } } break; } break; case 'zoom' : switch( $dirn ) { case 'Tele' : $factor = ($long_y-($y+1))/$long_y; break; case 'Wide' : $factor = ($y+1)/$long_y; break; } if ( $monitor->HasZoomSpeed() ) { $speed = intval(round($monitor->MinZoomSpeed()+(($monitor->MaxZoomSpeed()-$monitor->MinZoomSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinZoomStep()+(($monitor->MaxZoomStep()-$monitor->MinZoomStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; case 'Con' : if ( $monitor->AutoStopTimeout() ) { $slowSpeed = intval(round($monitor->MinZoomSpeed()+(($monitor->MaxZoomSpeed()-$monitor->MinZoomSpeed())*$slow))); if ( $speed < $slowSpeed ) { $ctrlCommand .= ' --autostop'; } } break; } break; case 'iris' : switch( $dirn ) { case 'Open' : $factor = ($long_y-($y+1))/$long_y; break; case 'Close' : $factor = ($y+1)/$long_y; break; } if ( $monitor->HasIrisSpeed() ) { $speed = intval(round($monitor->MinIrisSpeed()+(($monitor->MaxIrisSpeed()-$monitor->MinIrisSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinIrisStep()+(($monitor->MaxIrisStep()-$monitor->MinIrisStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; } break; case 'white' : switch( $dirn ) { case 'In' : $factor = ($long_y-($y+1))/$long_y; break; case 'Out' : $factor = ($y+1)/$long_y; break; } if ( $monitor->HasWhiteSpeed() ) { $speed = intval(round($monitor->MinWhiteSpeed()+(($monitor->MaxWhiteSpeed()-$monitor->MinWhiteSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinWhiteStep()+(($monitor->MaxWhiteStep()-$monitor->MinWhiteStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; } break; case 'gain' : switch( $dirn ) { case 'Up' : $factor = ($long_y-($y+1))/$long_y; break; case 'Down' : $factor = ($y+1)/$long_y; break; } if ( $monitor->HasGainSpeed() ) { $speed = intval(round($monitor->MinGainSpeed()+(($monitor->MaxGainSpeed()-$monitor->MinGainSpeed())*$factor))); $ctrlCommand .= ' --speed='.$speed; } switch( $mode ) { case 'Abs' : case 'Rel' : $step = intval(round($monitor->MinGainStep()+(($monitor->MaxGainStep()-$monitor->MinGainStep())*$factor))); $ctrlCommand .= ' --step='.$step; break; } break; case 'move' : $xFactor = 0; $yFactor = 0; if ( preg_match( '/^Up/', $dirn ) ) { $yFactor = ($short_y-($y+1))/$short_y; } elseif ( preg_match( '/^Down/', $dirn ) ) { $yFactor = ($y+1)/$short_y; } if ( preg_match( '/Left$/', $dirn ) ) { $xFactor = ($short_x-($x+1))/$short_x; } elseif ( preg_match( '/Right$/', $dirn ) ) { $xFactor = ($x+1)/$short_x; } if ( $monitor->Orientation() != '0' ) { $conversions = array( '90' => array( 'Up' => 'Left', 'Down' => 'Right', 'Left' => 'Down', 'Right' => 'Up', 'UpLeft' => 'DownLeft', 'UpRight' => 'UpLeft', 'DownLeft' => 'DownRight', 'DownRight' => 'UpRight', ), '180' => array( 'Up' => 'Down', 'Down' => 'Up', 'Left' => 'Right', 'Right' => 'Left', 'UpLeft' => 'DownRight', 'UpRight' => 'DownLeft', 'DownLeft' => 'UpRight', 'DownRight' => 'UpLeft', ), '270' => array( 'Up' => 'Right', 'Down' => 'Left', 'Left' => 'Up', 'Right' => 'Down', 'UpLeft' => 'UpRight', 'UpRight' => 'DownRight', 'DownLeft' => 'UpLeft', 'DownRight' => 'DownLeft', ), 'hori' => array( 'Up' => 'Up', 'Down' => 'Down', 'Left' => 'Right', 'Right' => 'Left', 'UpLeft' => 'UpRight', 'UpRight' => 'UpLeft', 'DownLeft' => 'DownRight', 'DownRight' => 'DownLeft', ), 'vert' => array( 'Up' => 'Down', 'Down' => 'Up', 'Left' => 'Left', 'Right' => 'Right', 'UpLeft' => 'DownLeft', 'UpRight' => 'DownRight', 'DownLeft' => 'UpLeft', 'DownRight' => 'UpRight', ), ); $new_dirn = $conversions[$monitor->Orientation()][$dirn]; $_REQUEST['control'] = preg_replace( "/_$dirn\$/", "_$new_dirn", $_REQUEST['control'] ); $dirn = $new_dirn; } if ( $monitor->HasPanSpeed() && $xFactor ) { if ( $monitor->HasTurboPan() ) { if ( $xFactor >= $turbo ) { $panSpeed = $monitor->TurboPanSpeed(); } else { $xFactor = $xFactor/$turbo; $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } } else { $panSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$xFactor))); } $ctrlCommand .= ' --panspeed='.$panSpeed; } if ( $monitor->HasTiltSpeed() && $yFactor ) { if ( $monitor->HasTurboTilt() ) { if ( $yFactor >= $turbo ) { $tiltSpeed = $monitor->TurboTiltSpeed(); } else { $yFactor = $yFactor/$turbo; $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } } else { $tiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$yFactor))); } $ctrlCommand .= ' --tiltspeed='.$tiltSpeed; } switch( $mode ) { case 'Rel' : case 'Abs' : if ( preg_match( '/(Left|Right)$/', $dirn ) ) { $panStep = intval(round($monitor->MinPanStep()+(($monitor->MaxPanStep()-$monitor->MinPanStep())*$xFactor))); $ctrlCommand .= ' --panstep='.$panStep; } if ( preg_match( '/^(Up|Down)/', $dirn ) ) { $tiltStep = intval(round($monitor->MinTiltStep()+(($monitor->MaxTiltStep()-$monitor->MinTiltStep())*$yFactor))); $ctrlCommand .= ' --tiltstep='.$tiltStep; } break; case 'Con' : if ( $monitor->AutoStopTimeout() ) { $slowPanSpeed = intval(round($monitor->MinPanSpeed()+(($monitor->MaxPanSpeed()-$monitor->MinPanSpeed())*$slow))); $slowTiltSpeed = intval(round($monitor->MinTiltSpeed()+(($monitor->MaxTiltSpeed()-$monitor->MinTiltSpeed())*$slow))); if ( (!isset($panSpeed) || ($panSpeed < $slowPanSpeed)) && (!isset($tiltSpeed) || ($tiltSpeed < $slowTiltSpeed)) ) { $ctrlCommand .= ' --autostop'; } } break; } } } } } else { if ( preg_match( '/^presetGoto(\d+)$/', $_REQUEST['control'], $matches ) ) { $_REQUEST['control'] = 'presetGoto'; $ctrlCommand .= ' --preset='.$matches[1]; } elseif ( $_REQUEST['control'] == 'presetGoto' && !empty($_REQUEST['preset']) ) { $ctrlCommand .= ' --preset='.$_REQUEST['preset']; } elseif ( $_REQUEST['control'] == 'presetSet' ) { if ( canEdit( 'Control' ) ) { $preset = validInt($_REQUEST['preset']); $newLabel = validJsStr($_REQUEST['newLabel']); $row = dbFetchOne( 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?', NULL, array( $monitor->Id(), $preset ) ); if ( $newLabel != $row['Label'] ) { if ( $newLabel ) { dbQuery( 'REPLACE INTO ControlPresets ( MonitorId, Preset, Label ) VALUES ( ?, ?, ? )', array( $monitor->Id(), $preset, $newLabel ) ); } else { dbQuery( 'DELETE FROM ControlPresets WHERE MonitorId = ? AND Preset = ?', array( $monitor->Id(), $preset ) ); } } $ctrlCommand .= ' --preset='.$preset; } $ctrlCommand .= ' --preset='.$preset; } elseif ( $_REQUEST['control'] == 'moveMap' ) { $ctrlCommand .= " --xcoord=$x --ycoord=$y"; } } $ctrlCommand .= ' --command='.$_REQUEST['control']; return( $ctrlCommand ); } function sendControlCommand($mid,$command) { // Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command. $socket = socket_create( AF_UNIX, SOCK_STREAM, 0 ); if ( $socket < 0 ) { Fatal( 'socket_create() failed: '.socket_strerror($socket) ); } $sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$mid.'.sock'; if ( @socket_connect( $socket, $sockFile ) ) { $options = array(); foreach ( explode( ' ', $command ) as $option ) { if ( preg_match( '/--([^=]+)(?:=(.+))?/', $option, $matches ) ) { $options[$matches[1]] = $matches[2]?$matches[2]:1; } } $optionString = jsonEncode( $options ); if ( !socket_write( $socket, $optionString ) ) { Fatal( "Can't write to control socket: ".socket_strerror(socket_last_error($socket)) ); } socket_close( $socket ); } else if ( $command != 'quit' ) { $command .= ' --id='.$mid; // Can't connect so use script $ctrlOutput = exec( escapeshellcmd( $command ) ); } } // end function sendControlCommand( $mid, $command ) ZoneMinder-1.32.2/web/includes/Control.php0000644000000000000000000001620413365153155017075 0ustar rootroot 0, 'CanMoveDiag' => 0, 'CanMoveMap' => 0, 'CanMoveAbs' => 0, 'CanMoveRel' => 0, 'CanMoveCon' => 0, 'CanPan' => 0, 'CanReset' => 0, 'CanSleep' => 0, 'CanWake' => 0, 'MinPanRange' => NULL, 'MaxPanRange' => NULL, 'MinPanStep' => NULL, 'MaxPanStep' => NULL, 'HasPanSpeed' => 0, 'MinPanSpeed' => NULL, 'MaxPanSpeed' => NULL, 'HasTurboPan' => 0, 'TurboPanSpeed' => NULL, 'CanTilt' => 0, 'MinTiltRange' => NULL, 'MaxTiltRange' => NULL, 'MinTiltStep' => NULL, 'MaxTiltStep' => NULL, 'HasTiltSpeed' => 0, 'MinTiltSpeed' => NULL, 'MaxTiltSpeed' => NULL, 'HasTurboTilt' => 0, 'TurboTiltSpeed' => NULL, 'CanZoom' => 0, 'CanZoomAbs' => 0, 'CanZoomRel' => 0, 'CanZoomCon' => 0, 'MinZoomRange' => NULL, 'MaxZoomRange' => NULL, 'MinZoomStep' => NULL, 'MaxZoomStep' => NULL, 'HasZoomSpeed' => 0, 'MinZoomSpeed' => NULL, 'MaxZoomSpeed' => NULL, 'CanFocus' => 0, 'CanAutoFocus' => 0, 'CanFocusAbs' => 0, 'CanFocusRel' => 0, 'CanFocusCon' => 0, 'MinFocusRange' => NULL, 'MaxFocusRange' => NULL, 'MinFocusStep' => NULL, 'MaxFocusStep' => NULL, 'HasFocusSpeed' => 0, 'MinFocusSpeed' => NULL, 'MaxFocusSpeed' => NULL, 'CanIris' => 0, 'CanAutoIris' => 0, 'CanIrisAbs' => 0, 'CanIrisRel' => 0, 'CanIrisCon' => 0, 'MinIrisRange' => NULL, 'MaxIrisRange' => NULL, 'MinIrisStep' => NULL, 'MaxIrisStep' => NULL, 'HasIrisSpeed' => 0, 'MinIrisSpeed' => NULL, 'MaxIrisSpeed' => NULL, 'CanGain' => 0, 'CanAutoGain' => 0, 'CanGainAbs' => 0, 'CanGainRel' => 0, 'CanGainCon' => 0, 'MinGainRange' => NULL, 'MaxGainRange' => NULL, 'MinGainStep' => NULL, 'MaxGainStep' => NULL, 'HasGainSpeed' => 0, 'MinGainSpeed' => NULL, 'MaxGainSpeed' => NULL, 'CanWhite' => 0, 'CanAutoWhite' => 0, 'CanWhiteAbs' => 0, 'CanWhiteRel' => 0, 'CanWhiteCon' => 0, 'MinWhiteRange' => NULL, 'MaxWhiteRange' => NULL, 'MinWhiteStep' => NULL, 'MaxWhiteStep' => NULL, 'HasWhiteSpeed' => 0, 'MinWhiteSpeed' => NULL, 'MaxWhiteSpeed' => NULL, 'HasPresets' => 0, 'NumPresets' => 0, 'HasHomePreset' => 0, 'CanSetPresets' => 0, 'Name' => 'New', 'Type' => 'Local', 'Protocol' => NULL ); public function __construct( $IdOrRow = NULL ) { if ( $IdOrRow ) { $row = NULL; if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { $row = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Control record for Id=" . $IdOrRow ); } } elseif ( is_array( $IdOrRow ) ) { $row = $IdOrRow; } else { Error("Unknown argument passed to Control Constructor ($IdOrRow)"); return; } if ( $row ) { foreach ($row as $k => $v) { $this->{$k} = $v; } } else { Error('No row for Control ' . $IdOrRow ); } } # end if isset($IdOrRow) } // end function __construct public function __call($fn, array $args){ if ( count($args) ) { $this->{$fn} = $args[0]; } if ( array_key_exists($fn, $this) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); } else { if ( array_key_exists($fn, $this->control_fields) ) { return $this->control_fields{$fn}; } else if ( array_key_exists( $fn, $this->defaults ) ) { return $this->defaults{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning( "Unknown function call Control->$fn from $file:$line" ); } } } public function set( $data ) { foreach ($data as $k => $v) { if ( is_array( $v ) ) { # perhaps should turn into a comma-separated string $this->{$k} = implode(',',$v); } else if ( is_string( $v ) ) { $this->{$k} = trim( $v ); } else if ( is_integer( $v ) ) { $this->{$k} = $v; } else if ( is_bool( $v ) ) { $this->{$k} = $v; } else { Error( "Unknown type $k => $v of var " . gettype( $v ) ); $this->{$k} = $v; } } } public static function find( $parameters = null, $options = null ) { $sql = 'SELECT * FROM Controls '; $values = array(); if ( $parameters ) { $fields = array(); $sql .= 'WHERE '; foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; } else if ( is_array( $value ) ) { $func = function(){return '?';}; $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; $values += $value; } else { $fields[] = $field.'=?'; $values[] = $value; } } $sql .= implode(' AND ', $fields ); } if ( $options ) { if ( isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } if ( isset($options['limit']) ) { if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { $sql .= ' LIMIT ' . $options['limit']; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Invalid value for limit(".$options['limit'].") passed to Control::find from $file:$line"); return; } } } $controls = array(); $result = dbQuery($sql, $values); $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Control'); foreach ( $results as $row => $obj ) { $controls[] = $obj; } return $controls; } public static function find_one( $parameters = array() ) { $results = Control::find( $parameters, array('limit'=>1) ); if ( ! sizeof($results) ) { return; } return $results[0]; } public function save( $new_values = null ) { if ( $new_values ) { foreach ( $new_values as $k=>$v ) { $this->{$k} = $v; } } // Set default values foreach ( $this->defaults as $k=>$v ) { if ( ( ! array_key_exists( $k, $this ) ) or ( $this->{$k} == '' ) ) { $this->{$k} = $v; } } $fields = array_keys( $this->defaults ); if ( array_key_exists( 'Id', $this ) ) { $sql = 'UPDATE Controls SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ' WHERE Id=?'; $values = array_map( function($field){return $this->{$field};}, $fields ); $values[] = $this->{'Id'}; dbQuery( $sql, $values ); } else { $sql = 'INSERT INTO Controls SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ''; $values = array_map( function($field){return $this->{$field};}, $fields ); dbQuery( $sql, $values ); $this->{'Id'} = dbInsertId(); } } // end function save } // end class Control ?> ZoneMinder-1.32.2/web/includes/csrf/0000755000000000000000000000000013365153155015676 5ustar rootrootZoneMinder-1.32.2/web/includes/csrf/csrf-magic.php0000644000000000000000000003517513365153155020435 0ustar rootroot */ $GLOBALS['csrf']['input-name'] = '__csrf_magic'; /** * Set this to false if your site must work inside of frame/iframe elements, * but do so at your own risk: this configuration protects you against CSS * overlay attacks that defeat tokens. */ $GLOBALS['csrf']['frame-breaker'] = true; /** * Whether or not CSRF Magic should be allowed to start a new session in order * to determine the key. */ $GLOBALS['csrf']['auto-session'] = true; /** * Whether or not csrf-magic should produce XHTML style tags. */ $GLOBALS['csrf']['xhtml'] = true; // FUNCTIONS: // Don't edit this! $GLOBALS['csrf']['version'] = '1.0.4'; /** * Rewrites on the fly to add CSRF tokens to them. This can also * inject our JavaScript library. */ function csrf_ob_handler($buffer, $flags) { // Even though the user told us to rewrite, we should do a quick heuristic // to check if the page is *actually* HTML. We don't begin rewriting until // we hit the first "; $buffer = preg_replace('#(]*method\s*=\s*["\']post["\'][^>]*>)#i', '$1' . $input, $buffer); if ($GLOBALS['csrf']['frame-breaker']) { $buffer = str_ireplace('', '', $buffer); } if ($js = $GLOBALS['csrf']['rewrite-js']) { $buffer = str_ireplace( '', ''. '', $buffer ); $script = ''; $buffer = str_ireplace('', $script . '', $buffer, $count); if (!$count) { $buffer .= $script; } } return $buffer; } /** * Checks if this is a post request, and if it is, checks if the nonce is valid. * @param bool $fatal Whether or not to fatally error out if there is a problem. * @return True if check passes or is not necessary, false if failure. */ function csrf_check($fatal = true) { if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; csrf_start(); $name = $GLOBALS['csrf']['input-name']; $ok = false; $tokens = ''; do { if (!isset($_POST[$name])) { #Logger::Debug("POST[$name] is not set"); break; #} else { #Logger::Debug("POST[$name] is set as " . $_POST[$name] ); } // we don't regenerate a token and check it because some token creation // schemes are volatile. $tokens = $_POST[$name]; if (!csrf_check_tokens($tokens)) { #Logger::Debug("Failed checking tokens"); break; #} else { #Logger::Debug("Token passed"); } $ok = true; } while (false); if ($fatal && !$ok) { $callback = $GLOBALS['csrf']['callback']; if (trim($tokens, 'A..Za..z0..9:;,') !== '') $tokens = 'hidden'; $callback($tokens); exit; } return $ok; } /** * Retrieves a valid token(s) for a particular context. Tokens are separated * by semicolons. */ function csrf_get_tokens() { $has_cookies = !empty($_COOKIE); // $ip implements a composite key, which is sent if the user hasn't sent // any cookies. It may or may not be used, depending on whether or not // the cookies "stick" $secret = csrf_get_secret(); if (!$has_cookies && $secret) { // :TODO: Harden this against proxy-spoofing attacks $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); $ip = ';ip:' . csrf_hash($IP_ADDRESS); } else { $ip = ''; } csrf_start(); // These are "strong" algorithms that don't require per se a secret if ($GLOBALS['csrf']['key']) return 'key:' . csrf_hash($GLOBALS['csrf']['key']) . $ip; if (session_id()) return 'sid:' . csrf_hash(session_id()) . $ip; if ($GLOBALS['csrf']['cookie']) { $val = csrf_generate_secret(); setcookie($GLOBALS['csrf']['cookie'], $val); return 'cookie:' . csrf_hash($val) . $ip; } // These further algorithms require a server-side secret if (!$secret) return 'invalid'; if ($GLOBALS['csrf']['user'] !== false) { return 'user:' . csrf_hash($GLOBALS['csrf']['user']); } if ($GLOBALS['csrf']['allow-ip']) { return ltrim($ip, ';'); } return 'invalid'; } function csrf_flattenpost($data) { $ret = array(); foreach($data as $n => $v) { $ret = array_merge($ret, csrf_flattenpost2(1, $n, $v)); } return $ret; } function csrf_flattenpost2($level, $key, $data) { if(!is_array($data)) return array($key => $data); $ret = array(); foreach($data as $n => $v) { $nk = $level >= 1 ? $key."[$n]" : "[$n]"; $ret = array_merge($ret, csrf_flattenpost2($level+1, $nk, $v)); } return $ret; } /** * @param $tokens is safe for HTML consumption */ function csrf_callback($tokens) { // (yes, $tokens is safe to echo without escaping) header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); $data = ''; foreach (csrf_flattenpost($_POST) as $key => $value) { if ($key == $GLOBALS['csrf']['input-name']) continue; $data .= ''; } echo "CSRF check failed

    CSRF check failed. Your form session may have expired, or you may not have cookies enabled.

    $data

    Debug: $tokens

    "; } /** * Checks if a composite token is valid. Outward facing code should use this * instead of csrf_check_token() */ function csrf_check_tokens($tokens) { if (is_string($tokens)) $tokens = explode(';', $tokens); foreach ($tokens as $token) { if (csrf_check_token($token)) return true; } return false; } /** * Checks if a token is valid. */ function csrf_check_token($token) { #Logger::Debug("Checking CSRF token $token"); if (strpos($token, ':') === false) { #Logger::Debug("Checking CSRF token $token bad because no :"); return false; } list($type, $value) = explode(':', $token, 2); if (strpos($value, ',') === false) { #Logger::Debug("Checking CSRF token $token bad because no ,"); return false; } list($x, $time) = explode(',', $token, 2); if ($GLOBALS['csrf']['expires']) { if (time() > $time + $GLOBALS['csrf']['expires']) { #Logger::Debug("Checking CSRF token $token bad because expired"); return false; } } switch ($type) { case 'sid': { #Logger::Debug("Checking sid: $value === " . csrf_hash(session_id(), $time) ); return $value === csrf_hash(session_id(), $time); } case 'cookie': $n = $GLOBALS['csrf']['cookie']; if (!$n) return false; if (!isset($_COOKIE[$n])) return false; return $value === csrf_hash($_COOKIE[$n], $time); case 'key': if (!$GLOBALS['csrf']['key']) { Logger::Debug("Checking key: no key set" ); return false; } #Logger::Debug("Checking sid: $value === " . csrf_hash($GLOBALS['csrf']['key'], $time) ); return $value === csrf_hash($GLOBALS['csrf']['key'], $time); // We could disable these 'weaker' checks if 'key' was set, but // that doesn't make me feel good then about the cookie-based // implementation. case 'user': if (!csrf_get_secret()) return false; if ($GLOBALS['csrf']['user'] === false) return false; return $value === csrf_hash($GLOBALS['csrf']['user'], $time); case 'ip': if (!csrf_get_secret()) return false; // do not allow IP-based checks if the username is set, or if // the browser sent cookies if ($GLOBALS['csrf']['user'] !== false) return false; if (!empty($_COOKIE)) return false; if (!$GLOBALS['csrf']['allow-ip']) return false; $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); return $value === csrf_hash($IP_ADDRESS, $time); } return false; } /** * Sets a configuration value. */ function csrf_conf($key, $val) { if (!isset($GLOBALS['csrf'][$key])) { trigger_error('No such configuration ' . $key, E_USER_WARNING); return; } $GLOBALS['csrf'][$key] = $val; } /** * Starts a session if we're allowed to. */ function csrf_start() { if ($GLOBALS['csrf']['auto-session'] && !session_id()) { session_start(); } } /** * Retrieves the secret, and generates one if necessary. */ function csrf_get_secret() { if ($GLOBALS['csrf']['secret']) return $GLOBALS['csrf']['secret']; $dir = dirname(__FILE__); $file = $dir . '/csrf-secret.php'; $secret = ''; if (file_exists($file)) { include $file; return $secret; } if (is_writable($dir)) { $secret = csrf_generate_secret(); $fh = fopen($file, 'w'); fwrite($fh, ' - A global declaration was omitted, resulting in a variable not being properly introduced in PHP 5.3. Thanks Whitney Beck for reporting. 1.0.2 released 2009-03-08 [SECURITY FIXES] - Due to a typo, csrf-magic accidentally treated the secret key as always present. This means that there was a possible CSRF attack against users without any cookies. No attacks in the wild were known at the time of this release. Thanks Jakub Vrรกna for reporting. 1.0.1 released 2008-11-02 [NEW FEATURES] - Support for composite tokens; this also fixes a bug with using IP-based tokens for users with cookies disabled. - Native support cookie tokens; use csrf_conf('cookie', $name) to specify the name of a cookie that the CSRF token should be placed in. This is useful if you have a Squid cache, and need to configure it to ignore this token. - Tips/tricks section in README.txt. - There is now a two hour expiration time on all tokens. This can be modified using csrf_conf('expires', $seconds). - ClickJacking protection using an iframe breaker. Disable with csrf_conf('frame-breaker', false). [BUG FIXES] - CsrfMagic.send() incorrectly submitted GET requests twice, once without the magic token and once with the token. Reported by Kelly Lu . ZoneMinder-1.32.2/web/includes/csrf/csrf-magic.js0000644000000000000000000001622113365153155020251 0ustar rootroot/** * @file * * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory * plays nice with other JavaScript libraries, needs testing though. */ // Here are the basic overloaded method definitions // The wrapper must be set BEFORE onreadystatechange is written to, since // a bug in ActiveXObject prevents us from properly testing for it. CsrfMagic = function(real) { // try to make it ourselves, if you didn't pass it if (!real) try { real = new XMLHttpRequest; } catch (e) {;} if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;} if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;} if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;} this.csrf = real; // properties var csrfMagic = this; real.onreadystatechange = function() { csrfMagic._updateProps(); return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null; }; csrfMagic._updateProps(); } CsrfMagic.prototype = { open: function(method, url, async, username, password) { if (method == 'POST') this.csrf_isPost = true; // deal with Opera bug, thanks jQuery if (username) return this.csrf_open(method, url, async, username, password); else return this.csrf_open(method, url, async); }, csrf_open: function(method, url, async, username, password) { if (username) return this.csrf.open(method, url, async, username, password); else return this.csrf.open(method, url, async); }, send: function(data) { if (!this.csrf_isPost) return this.csrf_send(data); prepend = csrfMagicName + '=' + csrfMagicToken + '&'; // XXX: Removed to eliminate 'Refused to set unsafe header "Content-length" ' errors in modern browsers // if (this.csrf_purportedLength === undefined) { // this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length); // delete this.csrf_purportedLength; // } delete this.csrf_isPost; return this.csrf_send(prepend + data); }, csrf_send: function(data) { return this.csrf.send(data); }, setRequestHeader: function(header, value) { // We have to auto-set this at the end, since we don't know how long the // nonce is when added to the data. if (this.csrf_isPost && header == "Content-length") { this.csrf_purportedLength = value; return; } return this.csrf_setRequestHeader(header, value); }, csrf_setRequestHeader: function(header, value) { return this.csrf.setRequestHeader(header, value); }, abort: function() { return this.csrf.abort(); }, getAllResponseHeaders: function() { return this.csrf.getAllResponseHeaders(); }, getResponseHeader: function(header) { return this.csrf.getResponseHeader(header); } // , } // proprietary CsrfMagic.prototype._updateProps = function() { this.readyState = this.csrf.readyState; if (this.readyState == 4) { this.responseText = this.csrf.responseText; this.responseXML = this.csrf.responseXML; this.status = this.csrf.status; this.statusText = this.csrf.statusText; } } CsrfMagic.process = function(base) { if(typeof base == 'object') { base[csrfMagicName] = csrfMagicToken; return base; } var prepend = csrfMagicName + '=' + csrfMagicToken; if (base) return prepend + '&' + base; return prepend; } // callback function for when everything on the page has loaded CsrfMagic.end = function() { // This rewrites forms AGAIN, so in case buffering didn't work this // certainly will. forms = document.getElementsByTagName('form'); for (var i = 0; i < forms.length; i++) { form = forms[i]; if (form.method.toUpperCase() !== 'POST') continue; if (form.elements[csrfMagicName]) continue; var input = document.createElement('input'); input.setAttribute('name', csrfMagicName); input.setAttribute('value', csrfMagicToken); input.setAttribute('type', 'hidden'); form.appendChild(input); } } // Sets things up for Mozilla/Opera/nice browsers // We very specifically match against Internet Explorer, since they haven't // implemented prototypes correctly yet. if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v') { var x = XMLHttpRequest.prototype; var c = CsrfMagic.prototype; // Save the original functions x.csrf_open = x.open; x.csrf_send = x.send; x.csrf_setRequestHeader = x.setRequestHeader; // Notice that CsrfMagic is itself an instantiatable object, but only // open, send and setRequestHeader are necessary as decorators. x.open = c.open; x.send = c.send; x.setRequestHeader = c.setRequestHeader; } else { // The only way we can do this is by modifying a library you have been // using. We support YUI, script.aculo.us, prototype, MooTools, // jQuery, Ext and Dojo. if (window.jQuery) { // jQuery didn't implement a new XMLHttpRequest function, so we have // to do this the hard way. jQuery.csrf_ajax = jQuery.ajax; jQuery.ajax = function( s ) { if (s.type && s.type.toUpperCase() == 'POST') { s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); if ( s.data && s.processData && typeof s.data != "string" ) { s.data = jQuery.param(s.data); } s.data = CsrfMagic.process(s.data); } return jQuery.csrf_ajax( s ); } } if (window.Prototype) { // This works for script.aculo.us too Ajax.csrf_getTransport = Ajax.getTransport; Ajax.getTransport = function() { return new CsrfMagic(Ajax.csrf_getTransport()); } } if (window.MooTools) { Browser.csrf_Request = Browser.Request; Browser.Request = function () { return new CsrfMagic(Browser.csrf_Request()); } } if (window.YAHOO) { // old YUI API YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject; YAHOO.util.Connect.createXhrObject = function (transaction) { obj = YAHOO.util.Connect.csrf_createXhrObject(transaction); obj.conn = new CsrfMagic(obj.conn); return obj; } } if (window.Ext) { // Ext can use other js libraries as loaders, so it has to come last // Ext's implementation is pretty identical to Yahoo's, but we duplicate // it for comprehensiveness's sake. Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject; Ext.lib.Ajax.createXhrObject = function (transaction) { obj = Ext.lib.Ajax.csrf_createXhrObject(transaction); obj.conn = new CsrfMagic(obj.conn); return obj; } } if (window.dojo) { // NOTE: this doesn't work with latest dojo dojo.csrf__xhrObj = dojo._xhrObj; dojo._xhrObj = function () { return new CsrfMagic(dojo.csrf__xhrObj()); } } } ZoneMinder-1.32.2/web/includes/logger.php0000644000000000000000000003756013365153156016745 0ustar rootroot "DBG", self::INFO => "INF", self::WARNING => "WAR", self::ERROR => "ERR", self::FATAL => "FAT", self::PANIC => "PNC", self::NOLOG => "OFF", ); private static $syslogPriorities = array( self::DEBUG => LOG_DEBUG, self::INFO => LOG_INFO, self::WARNING => LOG_WARNING, self::ERROR => LOG_ERR, self::FATAL => LOG_ERR, self::PANIC => LOG_ERR, ); private static $phpErrorLevels = array( self::DEBUG => E_USER_NOTICE, self::INFO => E_USER_NOTICE, self::WARNING => E_USER_WARNING, self::ERROR => E_USER_WARNING, self::FATAL => E_USER_ERROR, self::PANIC => E_USER_ERROR, ); private function __construct() { $this->hasTerm = (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])); $this->logFile = $this->logPath."/".$this->id.".log"; } public function __destruct() { $this->terminate(); } public function initialise( $options=array() ) { if ( !empty($options['id']) ) $this->id = $options['id']; //if ( isset($options['useErrorLog']) ) //$this->useErrorLog = $options['useErrorLog']; if ( isset($options['logPath']) ) { $this->logPath = $options['logPath']; $tempLogFile = $this->logPath."/".$this->id.".log"; } if ( isset($options['logFile']) ) $tempLogFile = $options['logFile']; else $tempLogFile = $this->logPath."/".$this->id.".log"; if ( !is_null($logFile = $this->getTargettedEnv('LOG_FILE')) ) $tempLogFile = $logFile; $tempLevel = self::INFO; $tempTermLevel = $this->termLevel; $tempDatabaseLevel = $this->databaseLevel; $tempFileLevel = $this->fileLevel; $tempSyslogLevel = $this->syslogLevel; $tempWeblogLevel = $this->weblogLevel; if ( isset($options['termLevel']) ) $tempTermLevel = $options['termLevel']; if ( isset($options['databaseLevel']) ) $tempDatabaseLevel = $options['databaseLevel']; else $tempDatabaseLevel = ZM_LOG_LEVEL_DATABASE; if ( isset($options['fileLevel']) ) $tempFileLevel = $options['fileLevel']; else $tempFileLevel = ZM_LOG_LEVEL_FILE; if ( isset($options['weblogLevel']) ) $tempWeblogLevel = $options['weblogLevel']; else $tempWeblogLevel = ZM_LOG_LEVEL_WEBLOG; if ( isset($options['syslogLevel']) ) $tempSyslogLevel = $options['syslogLevel']; else $tempSyslogLevel = ZM_LOG_LEVEL_SYSLOG; if ( $value = getenv('LOG_PRINT') ) $tempTermLevel = $value ? self::DEBUG : self::NOLOG; if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL')) ) $tempLevel = $level; if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) ) $tempTermLevel = $level; if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) ) $tempDatabaseLevel = $level; if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) ) $tempFileLevel = $level; if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) ) $tempSyslogLevel = $level; if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_WEBLOG')) ) $tempWeblogLevel = $level; if ( ZM_LOG_DEBUG ) { foreach ( explode( '|', ZM_LOG_DEBUG_TARGET ) as $target ) { if ( $target == $this->id || $target == '_'.$this->id || $target == $this->idRoot || $target == '_'.$this->idRoot || $target == '' ) { if ( ZM_LOG_DEBUG_LEVEL > self::NOLOG ) { $tempLevel = $this->limit( ZM_LOG_DEBUG_LEVEL ); if ( ZM_LOG_DEBUG_FILE != '' ) { $tempLogFile = ZM_LOG_DEBUG_FILE; $tempFileLevel = $tempLevel; } } } } // end foreach target } // end if DEBUG $this->logFile( $tempLogFile ); $this->termLevel( $tempTermLevel ); $this->databaseLevel( $tempDatabaseLevel ); $this->fileLevel( $tempFileLevel ); $this->syslogLevel( $tempSyslogLevel ); $this->weblogLevel( $tempWeblogLevel ); $this->level( $tempLevel ); $this->initialised = true; //Logger::Debug( "LogOpts: level=".self::$codes[$this->level]."/".self::$codes[$this->effectiveLevel].", screen=".self::$codes[$this->termLevel].", database=".self::$codes[$this->databaseLevel].", logfile=".self::$codes[$this->fileLevel]."->".$this->logFile.", weblog=".self::$codes[$this->weblogLevel].", syslog=".self::$codes[$this->syslogLevel] ); } private function terminate() { if ( $this->initialised ) { if ( $this->fileLevel > self::NOLOG ) $this->closeFile(); if ( $this->syslogLevel > self::NOLOG ) $this->closeSyslog(); } $this->initialised = false; } private function limit( $level ) { if ( $level > self::DEBUG ) return( self::DEBUG ); if ( $level < self::NOLOG ) return( self::NOLOG ); return( $level ); } private function getTargettedEnv( $name ) { $envName = $name."_".$this->id; $value = getenv( $envName ); if ( $value === false && $this->id != $this->idRoot ) $value = getenv( $name."_".$this->idRoot ); if ( $value === false ) $value = getenv( $name ); return( $value !== false ? $value : NULL ); } public static function fetch( $initialise=true ) { if ( !isset(self::$instance) ) { $class = __CLASS__; self::$instance = new $class; if ( $initialise ) self::$instance->initialise( array( 'id'=>'web_php', 'syslogLevel'=>self::INFO, 'weblogLevel'=>self::INFO ) ); } return self::$instance; } public static function Debug( $string ) { Logger::fetch()->logPrint( Logger::DEBUG, $string ); } public function id( $id=NULL ) { if ( isset($id) && $this->id != $id ) { // Remove whitespace $id = preg_replace( '/\S/', '', $id ); // Replace non-alphanum with underscore $id = preg_replace( '/[^a-zA-Z_]/', '_', $id ); if ( $this->id != $id ) { $this->id = $this->idRoot = $id; if ( preg_match( '/^([^_]+)_(.+)$/', $id, $matches ) ) { $this->idRoot = $matches[1]; $this->idArgs = $matches[2]; } } } return( $this->id ); } public function level( $level ) { if ( isset($level) ) { $lastLevel = $this->level; $this->level = $this->limit($level); $this->effectiveLevel = self::NOLOG; if ( $this->termLevel > $this->effectiveLevel ) $this->effectiveLevel = $this->termLevel; if ( $this->databaseLevel > $this->effectiveLevel ) $this->effectiveLevel = $this->databaseLevel; if ( $this->fileLevel > $this->effectiveLevel ) $this->effectiveLevel = $this->fileLevel; if ( $this->weblogLevel > $this->effectiveLevel ) $this->effectiveLevel = $this->weblogLevel; if ( $this->syslogLevel > $this->effectiveLevel ) $this->effectiveLevel = $this->syslogLevel; if ( $this->effectiveLevel > $this->level ) $this->effectiveLevel = $this->level; if ( !$this->hasTerm ) { if ( $lastLevel < self::DEBUG && $this->level >= self::DEBUG ) { $this->savedErrorReporting = error_reporting( E_ALL ); $this->savedDisplayErrors = ini_set( 'display_errors', true ); } elseif ( $lastLevel >= self::DEBUG && $this->level < self::DEBUG ) { error_reporting( $this->savedErrorReporting ); ini_set( 'display_errors', $this->savedDisplayErrors ); } } } return( $this->level ); } public function debugOn() { return( $this->effectiveLevel >= self::DEBUG ); } public function termLevel( $termLevel ) { if ( isset($termLevel) ) { $termLevel = $this->limit($termLevel); if ( $this->termLevel != $termLevel ) $this->termLevel = $termLevel; } return( $this->termLevel ); } public function databaseLevel( $databaseLevel=NULL ) { if ( !is_null($databaseLevel) ) { $databaseLevel = $this->limit($databaseLevel); if ( $this->databaseLevel != $databaseLevel ) { $this->databaseLevel = $databaseLevel; if ( $this->databaseLevel > self::NOLOG ) { if ( (include_once 'database.php') === FALSE ) { $this->databaseLevel = self::NOLOG; Warning( "Unable to write log entries to DB, database.php not found" ); } } } } return( $this->databaseLevel ); } public function fileLevel( $fileLevel ) { if ( isset($fileLevel) ) { $fileLevel = $this->limit($fileLevel); if ( $this->fileLevel != $fileLevel ) { if ( $this->fileLevel > self::NOLOG ) $this->closeFile(); $this->fileLevel = $fileLevel; if ( $this->fileLevel > self::NOLOG ) $this->openFile(); } } return( $this->fileLevel ); } public function weblogLevel( $weblogLevel ) { if ( isset($weblogLevel) ) { $weblogLevel = $this->limit($weblogLevel); if ( $this->weblogLevel != $weblogLevel ) { if ( $weblogLevel > self::NOLOG && $this->weblogLevel <= self::NOLOG ) { $this->savedLogErrors = ini_set( 'log_errors', true ); } elseif ( $weblogLevel <= self::NOLOG && $this->weblogLevel > self::NOLOG ) { ini_set( 'log_errors', $this->savedLogErrors ); } $this->weblogLevel = $weblogLevel; } } return( $this->weblogLevel ); } public function syslogLevel( $syslogLevel ) { if ( isset($syslogLevel) ) { $syslogLevel = $this->limit($syslogLevel); if ( $this->syslogLevel != $syslogLevel ) { if ( $this->syslogLevel > self::NOLOG ) $this->closeSyslog(); $this->syslogLevel = $syslogLevel; if ( $this->syslogLevel > self::NOLOG ) $this->openSyslog(); } } return( $this->syslogLevel ); } private function openSyslog() { openlog( $this->id, LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); } private function closeSyslog() { closelog(); } private function logFile( $logFile ) { if ( preg_match( '/^(.+)\+$/', $logFile, $matches ) ) $this->logFile = $matches[1].'.'.getmypid(); else $this->logFile = $logFile; } private function openFile() { if ( !$this->useErrorLog ) { if ( $this->logFd = fopen( $this->logFile, 'a+' ) ) { if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) { $error = error_get_last(); trigger_error( "Can't open log file '$logFile': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR ); } $this->fileLevel = self::NOLOG; } } } private function closeFile() { if ( $this->logFd ) fclose( $this->logFd ); } public function logPrint( $level, $string, $file=NULL, $line=NULL ) { if ( $level <= $this->effectiveLevel ) { $string = preg_replace( '/[\r\n]+$/', '', $string ); $code = self::$codes[$level]; $time = gettimeofday(); $message = sprintf( '%s.%06d %s[%d].%s [%s]', strftime( '%x %H:%M:%S', $time['sec'] ), $time['usec'], $this->id, getmypid(), $code, $string ); if ( is_null($file) ) { if ( $this->useErrorLog || $this->databaseLevel > self::NOLOG ) { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; if ( $this->hasTerm ) $rootPath = getcwd(); else $rootPath = $_SERVER['DOCUMENT_ROOT']; $file = preg_replace( '/^'.addcslashes($rootPath,'/').'\/?/', '', $file ); } } if ( $this->useErrorLog ) $message .= ' at '.$file.' line '.$line; else $message = $message; if ( $level <= $this->termLevel ) if ( $this->hasTerm ) print( $message."\n" ); else print( preg_replace( "/\n/", '
    ', htmlspecialchars($message) ).'
    ' ); if ( $level <= $this->fileLevel ) if ( $this->useErrorLog ) { if ( !error_log( $message."\n", 3, $this->logFile ) ) { if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) { $error = error_get_last(); trigger_error( "Can't write to log file '".$this->logFile."': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR ); } } } elseif ( $this->logFd ) { fprintf( $this->logFd, $message."\n" ); } $message = $code.' ['.$string.']'; if ( $level <= $this->syslogLevel ) syslog( self::$syslogPriorities[$level], $message ); if ( $level <= $this->databaseLevel ) { try { global $dbConn; $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, ? )'; $stmt = $dbConn->prepare( $sql ); $result = $stmt->execute( array( sprintf( '%d.%06d', $time['sec'], $time['usec'] ), $this->id, getmypid(), $level, $code, $string, $file, $line ) ); } catch(PDOException $ex) { $this->databaseLevel = self::NOLOG; Error("Can't write log entry '$sql': ". $ex->getMessage()); } } // This has to be last as trigger_error can be fatal if ( $level <= $this->weblogLevel ) { if ( $this->useErrorLog ) error_log( $message, 0 ); else trigger_error( $message, self::$phpErrorLevels[$level] ); } } } }; function logInit( $options=array() ) { $logger = Logger::fetch(); $logger->initialise( $options ); set_error_handler( 'ErrorHandler' ); } function logToDatabase( $level=NULL ) { return( Logger::fetch()->databaseLevel( $level ) ); } function Mark( $level=Logger::DEBUG, $tag='Mark' ) { Logger::fetch()->logPrint( $level, $tag ); } function Dump( &$var, $label='VAR' ) { ob_start(); print( $label.' => ' ); print_r( $var ); Logger::fetch()->logPrint( Logger::DEBUG, ob_get_clean() ); } function Info( $string ) { Logger::fetch()->logPrint( Logger::INFO, $string ); } function Warning( $string ) { Logger::fetch()->logPrint( Logger::WARNING, $string ); } function Error( $string ) { Logger::fetch()->logPrint( Logger::ERROR, $string ); } function Fatal( $string ) { Logger::fetch()->logPrint( Logger::FATAL, $string ); die( htmlentities($string) ); } function Panic( $string ) { if ( true ) { // Use builtin function ob_start(); debug_print_backtrace(); $backtrace = "\n".ob_get_clean(); } else { // Roll your own $backtrace = ''; $frames = debug_backtrace(); for ( $i = 0; $i < count($frames); $i++ ) { $frame = $frames[$i]; $backtrace .= sprintf( "\n#%d %s() at %s/%d", $i, $frame['function'], $frame['file'], $frame['line'] ); } } Logger::fetch()->logPrint( Logger::PANIC, $string.$backtrace ); die( $string ); } function ErrorHandler( $error, $string, $file, $line ) { if ( ! (error_reporting() & $error) ) { // This error code is not included in error_reporting return( false ); } switch ( $error ) { case E_USER_ERROR: Logger::fetch()->logPrint( Logger::FATAL, $string, $file, $line ); break; case E_USER_WARNING: Logger::fetch()->logPrint( Logger::ERROR, $string, $file, $line ); break; case E_USER_NOTICE: Logger::fetch()->logPrint( Logger::WARNING, $string, $file, $line ); break; default: Panic( "Unknown error type: [$error] $string" ); break; } return( true ); } ?> ZoneMinder-1.32.2/web/includes/actions.php0000644000000000000000000012076013365153155017120 0ustar rootroot array( 'method' => $method, 'content' => $data )); if ( $optional_headers !== null ) { $params['http']['header'] = $optional_headers; } $ctx = stream_context_create($params); $fp = @fopen($url, 'rb', false, $ctx); if ( !$fp ) { throw new Exception("Problem with $url, $php_errormsg"); } $response = @stream_get_contents($fp); if ( $response === false ) { throw new Exception("Problem reading data from $url, $php_errormsg"); } return $response; } function do_post_request($url, $data, $optional_headers = null) { $params = array('http' => array( 'method' => 'POST', 'content' => $data )); if ( $optional_headers !== null ) { $params['http']['header'] = $optional_headers; } $ctx = stream_context_create($params); $fp = @fopen($url, 'rb', false, $ctx); if ( !$fp ) { throw new Exception("Problem with $url, $php_errormsg"); } $response = @stream_get_contents($fp); if ( $response === false ) { throw new Exception("Problem reading data from $url, $php_errormsg"); } return $response; } function getAffectedIds( $name ) { $names = $name.'s'; $ids = array(); if ( isset($_REQUEST[$names]) ) { if ( is_array($_REQUEST[$names]) ) { $ids = $_REQUEST[$names]; } else { $ids = array($_REQUEST[$names]); } } else if ( isset($_REQUEST[$name]) ) { if ( is_array($_REQUEST[$name]) ) { $ids = $_REQUEST[$name]; } else { $ids = array($_REQUEST[$name]); } } return $ids; } if ( empty($action) ) { return; } if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { // if true, a popup will display after login // PP - lets validate reCaptcha if it exists if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY && ZM_OPT_GOOG_RECAPTCHA_SITEKEY ) { $url = 'https://www.google.com/recaptcha/api/siteverify'; $fields = array ( 'secret' => ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, 'response' => $_REQUEST['g-recaptcha-response'], 'remoteip' => $_SERVER['REMOTE_ADDR'] ); $res = do_post_request($url, http_build_query($fields)); $responseData = json_decode($res,true); // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php // if recaptcha resulted in error, we might have to deny login if ( isset($responseData['success']) && $responseData['success'] == false ) { // PP - before we deny auth, let's make sure the error was not 'invalid secret' // because that means the user did not configure the secret key correctly // in this case, we prefer to let him login in and display a message to correct // the key. Unfortunately, there is no way to check for invalid site key in code // as it produces the same error as when you don't answer a recaptcha if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) { if ( !in_array('invalid-input-secret',$responseData['error-codes']) ) { Error('reCaptcha authentication failed'); userLogout(); $view = 'login'; $refreshParent = true; return; } else { //Let them login but show an error echo ''; Error('Invalid recaptcha secret detected'); } } } // end if success==false } // end if using reCaptcha $username = validStr($_REQUEST['username']); $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; userLogin($username, $password); $refreshParent = true; $view = 'console'; $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; } else if ( $action == 'logout' ) { userLogout(); $refreshParent = true; $view = 'none'; } else if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); setcookie('zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10); $refreshParent = true; } // Event scope actions, view permissions only required if ( canView('Events') ) { if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { if ( $action == 'addterm' ) { $_REQUEST['filter'] = addFilterTerm($_REQUEST['filter'], $_REQUEST['line']); } elseif ( $action == 'delterm' ) { $_REQUEST['filter'] = delFilterTerm($_REQUEST['filter'], $_REQUEST['line']); } else if ( canEdit('Events') ) { if ( $action == 'delete' ) { if ( ! empty($_REQUEST['Id']) ) { dbQuery('DELETE FROM Filters WHERE Id=?', array($_REQUEST['Id'])); } } else if ( ( $action == 'Save' ) or ( $action == 'SaveAs' ) or ( $action == 'execute' ) ) { # or ( $action == 'submit' ) ) { $sql = ''; $_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']); $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); if ( $action == 'execute' ) { $tempFilterName = '_TempFilter'.time(); $sql .= ' Name = \''.$tempFilterName.'\''; } else { $sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); } $sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query'])); $sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0); $sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0); $sql .= ', AutoUpload = '. ( !empty($_REQUEST['filter']['AutoUpload']) ? 1 : 0); $sql .= ', AutoEmail = '. ( !empty($_REQUEST['filter']['AutoEmail']) ? 1 : 0); $sql .= ', AutoMessage = '. ( !empty($_REQUEST['filter']['AutoMessage']) ? 1 : 0); $sql .= ', AutoExecute = '. ( !empty($_REQUEST['filter']['AutoExecute']) ? 1 : 0); $sql .= ', AutoExecuteCmd = '.dbEscape($_REQUEST['filter']['AutoExecuteCmd']); $sql .= ', AutoDelete = '. ( !empty($_REQUEST['filter']['AutoDelete']) ? 1 : 0); if ( !empty($_REQUEST['filter']['AutoMove']) ? 1 : 0) { $sql .= ', AutoMove = 1, AutoMoveTo='. validInt($_REQUEST['filter']['AutoMoveTo']); } else { $sql .= ', AutoMove = 0'; } $sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0); $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) { dbQuery('UPDATE Filters SET ' . $sql. ' WHERE Id=?', array($_REQUEST['Id'])); } else { dbQuery('INSERT INTO Filters SET' . $sql); $_REQUEST['Id'] = dbInsertId(); } if ( $action == 'execute' ) { executeFilter( $tempFilterName ); } } // end if save or execute } // end if canEdit(Events) return; } // end if object == filter else { // Event scope actions, edit permissions required if ( canEdit('Events') ) { if ( ($action == 'rename') && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) { dbQuery('UPDATE Events SET Name=? WHERE Id=?', array($_REQUEST['eventName'], $_REQUEST['eid'])); } else if ( $action == 'eventdetail' ) { if ( !empty($_REQUEST['eid']) ) { dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid']) ); } else { $dbConn->beginTransaction(); foreach( getAffectedIds('markEid') as $markEid ) { dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid) ); } $dbConn->commit(); } $refreshParent = true; $closePopup = true; } elseif ( $action == 'archive' || $action == 'unarchive' ) { $archiveVal = ($action == 'archive')?1:0; if ( !empty($_REQUEST['eid']) ) { dbQuery('UPDATE Events SET Archived=? WHERE Id=?', array($archiveVal, $_REQUEST['eid'])); } else { $dbConn->beginTransaction(); foreach( getAffectedIds('markEid') as $markEid ) { dbQuery('UPDATE Events SET Archived=? WHERE Id=?', array($archiveVal, $markEid)); } $dbConn->commit(); $refreshParent = true; } } elseif ( $action == 'delete' ) { $dbConn->beginTransaction(); foreach( getAffectedIds('markEid') as $markEid ) { deleteEvent($markEid); } $dbConn->commit(); $refreshParent = true; } } // end if canEdit(Events) } // end if filter or something else } // end canView(Events) // Monitor control actions, require a monitor id and control view permissions for that monitor if ( !empty($_REQUEST['mid']) && canView('Control', $_REQUEST['mid']) ) { require_once('control_functions.php'); require_once('Monitor.php'); $mid = validInt($_REQUEST['mid']); if ( $action == 'control' ) { $monitor = new Monitor($mid); $ctrlCommand = buildControlCommand($monitor); sendControlCommand($monitor->Id(), $ctrlCommand); } else if ( $action == 'settings' ) { $args = ' -m ' . escapeshellarg($mid); $args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']); $args .= ' -C' . escapeshellarg($_REQUEST['newContrast']); $args .= ' -H' . escapeshellarg($_REQUEST['newHue']); $args .= ' -O' . escapeshellarg($_REQUEST['newColour']); $zmuCommand = getZmuCommand($args); $zmuOutput = exec($zmuCommand); list($brightness, $contrast, $hue, $colour) = explode(' ', $zmuOutput); dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid)); } } // Control capability actions, require control edit permissions if ( canEdit('Control') ) { if ( $action == 'controlcap' ) { require_once('Control.php'); $Control = new Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null ); //$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); $Control->save($_REQUEST['newControl']); $refreshParent = true; $view = 'none'; } elseif ( $action == 'delete' ) { if ( isset($_REQUEST['markCids']) ) { foreach( $_REQUEST['markCids'] as $markCid ) { dbQuery('DELETE FROM Controls WHERE Id = ?', array($markCid)); dbQuery('UPDATE Monitors SET Controllable = 0, ControlId = 0 WHERE ControlId = ?', array($markCid)); $refreshParent = true; } } } // end if action } // end if canEdit Controls if ( isset($_REQUEST['object']) and $_REQUEST['object'] == 'Monitor' ) { if ( $action == 'save' ) { foreach ( $_REQUEST['mids'] as $mid ) { $mid = ValidInt($mid); if ( ! canEdit('Monitors', $mid) ) { Warning("Cannot edit monitor $mid"); continue; } $Monitor = new Monitor($mid); if ( $Monitor->Type() != 'WebSite' ) { $Monitor->zmaControl('stop'); $Monitor->zmcControl('stop'); } $Monitor->save($_REQUEST['newMonitor']); if ( $Monitor->Function() != 'None' && $Monitor->Type() != 'WebSite' ) { $Monitor->zmcControl('start'); if ( $Monitor->Enabled() ) { $Monitor->zmaControl('start'); } } } // end foreach mid $refreshParent = true; } // end if action == save } // end if object is Monitor // Monitor edit actions, require a monitor id and edit permissions for that monitor if ( !empty($_REQUEST['mid']) && canEdit('Monitors', $_REQUEST['mid']) ) { $mid = validInt($_REQUEST['mid']); if ( $action == 'function' ) { $monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id=?', NULL, array($mid)); $newFunction = validStr($_REQUEST['newFunction']); # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 $newEnabled = ( !isset($_REQUEST['newEnabled']) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; $oldFunction = $monitor['Function']; $oldEnabled = $monitor['Enabled']; if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { dbQuery('UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', array($newFunction, $newEnabled, $mid)); $monitor['Function'] = $newFunction; $monitor['Enabled'] = $newEnabled; if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) { $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); zmaControl($monitor, 'stop'); zmcControl($monitor, $restart?'restart':''); zmaControl($monitor, 'start'); } $refreshParent = true; } } else if ( $action == 'zone' && isset($_REQUEST['zid']) ) { $zid = validInt($_REQUEST['zid']); $monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id=?', NULL, array($mid)); if ( !empty($zid) ) { $zone = dbFetchOne('SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array($mid, $zid)); } else { $zone = array(); } if ( $_REQUEST['newZone']['Units'] == 'Percent' ) { $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); } unset( $_REQUEST['newZone']['Points'] ); $types = array(); $changes = getFormChanges($zone, $_REQUEST['newZone'], $types); if ( count($changes) ) { if ( $zid > 0 ) { dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?', array($mid, $zid)); } else { dbQuery('INSERT INTO Zones SET MonitorId=?, '.implode(', ', $changes), array($mid)); } if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) { if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { zmaControl($monitor, 'stop'); zmcControl($monitor, 'restart'); zmaControl($monitor, 'start'); } else { zmaControl($monitor, 'restart'); } } if ( ($_REQUEST['newZone']['Type'] == 'Privacy') && $monitor['Controllable'] ) { require_once('control_functions.php'); sendControlCommand($mid, 'quit'); } $refreshParent = true; } $view = 'none'; } elseif ( $action == 'plugin' && isset($_REQUEST['pl']) ) { $sql = 'SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?'; $pconfs=dbFetchAll($sql, NULL, array($mid, $_REQUEST['zid'], $_REQUEST['pl'])); $changes = 0; foreach ( $pconfs as $pconf ) { $value = $_REQUEST['pluginOpt'][$pconf['Name']]; if ( array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value'] != $value) ) { dbQuery('UPDATE PluginsConfig SET Value=? WHERE id=?', array($value, $pconf['Id'])); $changes++; } } if ( $changes > 0 ) { if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) { zmaControl($mid, 'restart'); } $refreshParent = true; } $view = 'none'; } elseif ( ($action == 'sequence') && isset($_REQUEST['smid']) ) { $smid = validInt($_REQUEST['smid']); $monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid)); $smonitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($smid)); dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($smonitor['Sequence'], $monitor['Id'])); dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($monitor['Sequence'], $smonitor['Id'])); $refreshParent = true; fixSequences(); } elseif ( $action == 'delete' ) { if ( isset($_REQUEST['markZids']) ) { $deletedZid = 0; foreach ( $_REQUEST['markZids'] as $markZid ) { $zone = dbFetchOne('SELECT * FROM Zones WHERE Id=?', NULL, array($markZid)); dbQuery('DELETE FROM Zones WHERE MonitorId=? AND Id=?', array($mid, $markZid)); $deletedZid = 1; } if ( $deletedZid ) { if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) { if ( $zone['Type'] == 'Privacy' ) { zmaControl($mid, 'stop'); zmcControl($mid, 'restart'); zmaControl($mid, 'start'); } else { zmaControl($mid, 'restart'); } } // end if daemonCheck() $refreshParent = true; } // end if deletedzid } // end if isset($_REQUEST['markZids']) } // end if action } // end if $mid and canEdit($mid) // Monitor edit actions, monitor id derived, require edit permissions for that monitor if ( canEdit('Monitors') ) { if ( $action == 'monitor' ) { $mid = 0; if ( !empty($_REQUEST['mid']) ) { $mid = validInt($_REQUEST['mid']); $monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id=?', NULL, array($mid)); if ( ZM_OPT_X10 ) { $x10Monitor = dbFetchOne('SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid)); if ( !$x10Monitor ) $x10Monitor = array(); } } else { $monitor = array(); if ( ZM_OPT_X10 ) { $x10Monitor = array(); } } $Monitor = new Monitor($monitor); // Define a field type for anything that's not simple text equivalent $types = array( 'Triggers' => 'set', 'Controllable' => 'toggle', 'TrackMotion' => 'toggle', 'Enabled' => 'toggle', 'DoNativeMotDet' => 'toggle', 'Exif' => 'toggle', 'RTSPDescribe' => 'toggle', 'RecordAudio' => 'toggle', 'Method' => 'raw', ); if ( $_REQUEST['newMonitor']['ServerId'] == 'auto' ) { $_REQUEST['newMonitor']['ServerId'] = dbFetchOne( 'SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id'); Logger::Debug('Auto selecting server: Got ' . $_REQUEST['newMonitor']['ServerId'] ); if ( ( ! $_REQUEST['newMonitor'] ) and defined('ZM_SERVER_ID') ) { $_REQUEST['newMonitor']['ServerId'] = ZM_SERVER_ID; Logger::Debug('Auto selecting server to ' . ZM_SERVER_ID); } } $columns = getTableColumns('Monitors'); $changes = getFormChanges($monitor, $_REQUEST['newMonitor'], $types, $columns); if ( count($changes) ) { if ( $mid ) { # If we change anything that changes the shared mem size, zma can complain. So let's stop first. if ( $monitor['Type'] != 'WebSite' ) { zmaControl($monitor, 'stop'); zmcControl($monitor, 'stop'); } dbQuery('UPDATE Monitors SET '.implode(', ', $changes).' WHERE Id=?', array($mid)); // Groups will be added below if ( isset($changes['Name']) or isset($changes['StorageId']) ) { $OldStorage = new Storage($monitor['StorageId']); $saferOldName = basename($monitor['Name']); if ( file_exists($OldStorage->Path().'/'.$saferOldName) ) unlink($OldStorage->Path().'/'.$saferOldName); $NewStorage = new Storage($_REQUEST['newMonitor']['StorageId']); if ( ! file_exists($NewStorage->Path().'/'.$mid) ) mkdir($NewStorage->Path().'/'.$mid, 0755); $saferNewName = basename($_REQUEST['newMonitor']['Name']); symlink($mid, $NewStorage->Path().'/'.$saferNewName); } if ( isset($changes['Width']) || isset($changes['Height']) ) { $newW = $_REQUEST['newMonitor']['Width']; $newH = $_REQUEST['newMonitor']['Height']; $newA = $newW * $newH; $oldW = $monitor['Width']; $oldH = $monitor['Height']; $oldA = $oldW * $oldH; $zones = dbFetchAll('SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid)); foreach ( $zones as $zone ) { $newZone = $zone; $points = coordsToPoints($zone['Coords']); for ( $i = 0; $i < count($points); $i++ ) { $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); } $newZone['Coords'] = pointsToCoords($points); $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); $changes = getFormChanges($zone, $newZone, $types); if ( count($changes) ) { dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?', array($mid, $zone['Id'])); } } // end foreach zone } // end if width and height $restart = true; } else if ( ! $user['MonitorIds'] ) { // Can only create new monitors if we are not restricted to specific monitors # FIXME This is actually a race condition. Should lock the table. $maxSeq = dbFetchOne('SELECT MAX(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence'); $changes[] = 'Sequence = '.($maxSeq+1); if ( dbQuery('INSERT INTO Monitors SET '.implode(', ', $changes)) ) { $mid = dbInsertId(); $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; dbQuery("INSERT INTO Zones SET MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); //$view = 'none'; $Storage = new Storage($_REQUEST['newMonitor']['StorageId']); mkdir($Storage->Path().'/'.$mid, 0755); $saferName = basename($_REQUEST['newMonitor']['Name']); symlink($mid, $Storage->Path().'/'.$saferName); } else { Error('Error saving new Monitor.'); return; } } else { Error('Users with Monitors restrictions cannot create new monitors.'); return; } $restart = true; } else { Logger::Debug('No action due to no changes to Monitor'); } # end if count(changes) if ( ( !isset($_POST['newMonitor']['GroupIds']) ) or ( count($_POST['newMonitor']['GroupIds']) != count($Monitor->GroupIds()) ) or array_diff($_POST['newMonitor']['GroupIds'], $Monitor->GroupIds()) ) { if ( $Monitor->Id() ) dbQuery('DELETE FROM Groups_Monitors WHERE MonitorId=?', array($mid)); if ( isset($_POST['newMonitor']['GroupIds']) ) { foreach ( $_POST['newMonitor']['GroupIds'] as $group_id ) { dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid)); } } } // end if there has been a change of groups if ( ZM_OPT_X10 ) { $x10Changes = getFormChanges($x10Monitor, $_REQUEST['newX10Monitor']); if ( count($x10Changes) ) { if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) { dbQuery('UPDATE TriggersX10 SET '.implode(', ', $x10Changes).' WHERE MonitorId=?', array($mid)); } elseif ( !$user['MonitorIds'] ) { if ( !$x10Monitor ) { dbQuery('INSERT INTO TriggersX10 SET MonitorId = ?, '.implode(', ', $x10Changes), array($mid)); } else { dbQuery('DELETE FROM TriggersX10 WHERE MonitorId = ?', array($mid)); } } $restart = true; } # end if has x10Changes } # end if ZM_OPT_X10 if ( $restart ) { $new_monitor = new Monitor($mid); //fixDevices(); if ( $monitor['Type'] != 'WebSite' ) { $new_monitor->zmcControl('start'); $new_monitor->zmaControl('start'); } if ( $new_monitor->Controllable() ) { require_once('control_functions.php'); sendControlCommand($mid, 'quit'); } // really should thump zmwatch and maybe zmtrigger too. //daemonControl( 'restart', 'zmwatch.pl' ); $refreshParent = true; } // end if restart $view = 'none'; } elseif ( $action == 'delete' ) { if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) { require_once('Monitor.php'); foreach ( $_REQUEST['markMids'] as $markMid ) { if ( canEdit('Monitors', $markMid) ) { // This could be faster as a select all if ( $monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid)) ) { $Monitor = new Monitor($monitor); $Monitor->delete(); } // end if monitor found in db } // end if canedit this monitor } // end foreach monitor in MarkMid } // markMids is set and we aren't limited to specific monitors } // end if action == Delete } // Device view actions if ( canEdit('Devices') ) { if ( $action == 'device' ) { if ( !empty($_REQUEST['command']) ) { setDeviceStatusX10($_REQUEST['key'], $_REQUEST['command']); } else if ( isset($_REQUEST['newDevice']) ) { if ( isset($_REQUEST['did']) ) { dbQuery('UPDATE Devices SET Name=?, KeyString=? WHERE Id=?', array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); } else { dbQuery('INSERT INTO Devices SET Name=?, KeyString=?', array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString']) ); } $refreshParent = true; $view = 'none'; } } elseif ( $action == 'delete' ) { if ( isset($_REQUEST['markDids']) ) { foreach( $_REQUEST['markDids'] as $markDid ) { dbQuery('DELETE FROM Devices WHERE Id=?', array($markDid)); $refreshParent = true; } } } // end if action } // end if canedit devices // Group view actions if ( canView('Groups') && ($action == 'setgroup') ) { if ( !empty($_REQUEST['gid']) ) { setcookie('zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10); } else { setcookie('zmGroup', '', time()-3600*24*2); } $refreshParent = true; } // Group edit actions # Should probably verify that each monitor id is a valid monitor, that we have access to. # However at the moment, you have to have System permissions to do this if ( canEdit('Groups') ) { if ( $action == 'group' ) { $monitors = empty($_POST['newGroup']['MonitorIds']) ? '' : implode(',', $_POST['newGroup']['MonitorIds']); $group_id = null; if ( !empty($_POST['gid']) ) { $group_id = $_POST['gid']; dbQuery( 'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?', array( $_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $group_id, ) ); dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($group_id)); } else { dbQuery( 'INSERT INTO Groups (Name,ParentId) VALUES (?,?)', array( $_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), ) ); $group_id = dbInsertId(); } if ( $group_id ) { foreach ( $_POST['newGroup']['MonitorIds'] as $mid ) { dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid)); } } $view = 'none'; $refreshParent = true; } else if ( $action == 'delete' ) { if ( !empty($_REQUEST['gid']) ) { if ( is_array($_REQUEST['gid']) ) { foreach ( $_REQUEST['gid'] as $gid ) { $Group = new Group($gid); $Group->delete(); } } else { $Group = new Group($_REQUEST['gid'] ); $Group->delete(); } } $refreshParent = true; } # end if action } // end if can edit groups // System edit actions if ( canEdit('System') ) { if ( isset($_REQUEST['object']) ) { if ( $_REQUEST['object'] == 'MontageLayout' ) { require_once('MontageLayout.php'); if ( $action == 'Save' ) { $Layout = null; if ( $_REQUEST['Name'] != '' ) { $Layout = new MontageLayout(); $Layout->Name($_REQUEST['Name']); } else { $Layout = new MontageLayout($_REQUEST['zmMontageLayout']); } $Layout->Positions($_REQUEST['Positions']); $Layout->save(); session_start(); $_SESSION['zmMontageLayout'] = $Layout->Id(); setcookie('zmMontageLayout', $Layout->Id(), 1); session_write_close(); $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=montagereview'; } // end if save } else if ( $_REQUEST['object'] == 'server' ) { if ( $action == 'Save' ) { if ( !empty($_REQUEST['id']) ) { $dbServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array($_REQUEST['id']) ); } else { $dbServer = array(); } $types = array(); $changes = getFormChanges($dbServer, $_REQUEST['newServer'], $types); if ( count($changes) ) { if ( !empty($_REQUEST['id']) ) { dbQuery('UPDATE Servers SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['id']) ); } else { dbQuery('INSERT INTO Servers SET '.implode(', ', $changes)); } $refreshParent = true; } $view = 'none'; } else if ( $action == 'delete' ) { if ( !empty($_REQUEST['markIds']) ) { foreach( $_REQUEST['markIds'] as $Id ) dbQuery('DELETE FROM Servers WHERE Id=?', array($Id)); } $refreshParent = true; } else { Error("Unknown action $action in saving Server"); } } else if ( $_REQUEST['object'] == 'storage' ) { if ( $action == 'Save' ) { if ( !empty($_REQUEST['id']) ) $dbStorage = dbFetchOne('SELECT * FROM Storage WHERE Id=?', NULL, array($_REQUEST['id'])); else $dbStorage = array(); $types = array(); $changes = getFormChanges($dbStorage, $_REQUEST['newStorage'], $types); if ( count($changes) ) { if ( !empty($_REQUEST['id']) ) { dbQuery('UPDATE Storage SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['id'])); } else { dbQuery('INSERT INTO Storage set '.implode(', ', $changes)); } $refreshParent = true; } $view = 'none'; } else if ( $action == 'delete' ) { if ( !empty($_REQUEST['markIds']) ) { foreach( $_REQUEST['markIds'] as $Id ) dbQuery('DELETE FROM Storage WHERE Id=?', array($Id)); } $refreshParent = true; } else { Error("Unknown action $action in saving Storage"); } } # end if isset($_REQUEST['object'] ) } else if ( $action == 'version' && isset($_REQUEST['option']) ) { $option = $_REQUEST['option']; switch( $option ) { case 'go' : { // Ignore this, the caller will open the page itself break; } case 'ignore' : { dbQuery("UPDATE Config SET Value = '".ZM_DYN_LAST_VERSION."' WHERE Name = 'ZM_DYN_CURR_VERSION'"); break; } case 'hour' : case 'day' : case 'week' : { $nextReminder = time(); if ( $option == 'hour' ) { $nextReminder += 60*60; } elseif ( $option == 'day' ) { $nextReminder += 24*60*60; } elseif ( $option == 'week' ) { $nextReminder += 7*24*60*60; } dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_NEXT_REMINDER'"); break; } case 'never' : { dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_CHECK_FOR_UPDATES'"); break; } } } if ( $action == 'donate' && isset($_REQUEST['option']) ) { $option = $_REQUEST['option']; switch( $option ) { case 'go' : { // Ignore this, the caller will open the page itself break; } case 'hour' : case 'day' : case 'week' : case 'month' : { $nextReminder = time(); if ( $option == 'hour' ) { $nextReminder += 60*60; } elseif ( $option == 'day' ) { $nextReminder += 24*60*60; } elseif ( $option == 'week' ) { $nextReminder += 7*24*60*60; } elseif ( $option == 'month' ) { $nextReminder += 30*24*60*60; } dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_DONATE_REMINDER_TIME'"); break; } case 'never' : case 'already' : { dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_DYN_SHOW_DONATE_REMINDER'"); break; } } // end switch option } if ( ($action == 'privacy') && isset($_REQUEST['option']) ) { switch( $_REQUEST['option'] ) { case 'decline' : { dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'"); dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_TELEMETRY_DATA'"); $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; break; } case 'accept' : { dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'"); dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_TELEMETRY_DATA'"); $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; break; } default: # Enable the privacy statement if we somehow submit something other than accept or decline dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_SHOW_PRIVACY'"); } // end switch option return; } if ( $action == 'options' && isset($_REQUEST['tab']) ) { $configCat = $configCats[$_REQUEST['tab']]; $changed = false; foreach ( $configCat as $name=>$value ) { unset($newValue); if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) { $newValue = 0; } else if ( isset($_REQUEST['newConfig'][$name]) ) { $newValue = preg_replace("/\r\n/", "\n", stripslashes($_REQUEST['newConfig'][$name])); } if ( isset($newValue) && ($newValue != $value['Value']) ) { dbQuery('UPDATE Config SET Value=? WHERE Name=?', array($newValue, $name)); $changed = true; } } if ( $changed ) { switch( $_REQUEST['tab'] ) { case 'system' : case 'config' : $restartWarning = true; break; case 'web' : case 'tools' : break; case 'logging' : case 'network' : case 'mail' : case 'upload' : $restartWarning = true; break; case 'highband' : case 'medband' : case 'lowband' : break; } $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=options&tab='.$_REQUEST['tab']; } loadConfig(false); return; } elseif ( $action == 'user' ) { if ( !empty($_REQUEST['uid']) ) $dbUser = dbFetchOne('SELECT * FROM Users WHERE Id=?', NULL, array($_REQUEST['uid'])); else $dbUser = array(); $types = array(); $changes = getFormChanges($dbUser, $_REQUEST['newUser'], $types); if ( $_REQUEST['newUser']['Password'] ) $changes['Password'] = 'Password = password('.dbEscape($_REQUEST['newUser']['Password']).')'; else unset($changes['Password']); if ( count($changes) ) { if ( !empty($_REQUEST['uid']) ) { dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['uid'])); # If we are updating the logged in user, then update our session user data. if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) userLogin($dbUser['Username'], $dbUser['Password']); } else { dbQuery('INSERT INTO Users SET '.implode(', ', $changes)); } $refreshParent = true; } $view = 'none'; } elseif ( $action == 'state' ) { if ( !empty($_REQUEST['runState']) ) { //if ( $cookies ) session_write_close(); packageControl($_REQUEST['runState']); $refreshParent = true; } } elseif ( $action == 'save' ) { if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) { $sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id'; $definitions = array(); foreach( dbFetchAll($sql) as $monitor ) { $definitions[] = $monitor['Id'].':'.$monitor['Function'].':'.$monitor['Enabled']; } $definition = join(',', $definitions); if ( $_REQUEST['newState'] ) $_REQUEST['runState'] = $_REQUEST['newState']; dbQuery('REPLACE INTO States SET Name=?, Definition=?', array($_REQUEST['runState'],$definition)); } } elseif ( $action == 'delete' ) { if ( isset($_REQUEST['runState']) ) dbQuery('DELETE FROM States WHERE Name=?', array($_REQUEST['runState'])); if ( isset($_REQUEST['markUids']) ) { foreach( $_REQUEST['markUids'] as $markUid ) dbQuery('DELETE FROM Users WHERE Id = ?', array($markUid)); if ( $markUid == $user['Id'] ) userLogout(); } } } else { if ( ZM_USER_SELF_EDIT && $action == 'user' ) { $uid = $user['Id']; $dbUser = dbFetchOne('SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid)); $types = array(); $changes = getFormChanges($dbUser, $_REQUEST['newUser'], $types); if ( !empty($_REQUEST['newUser']['Password']) ) $changes['Password'] = 'Password = password('.dbEscape($_REQUEST['newUser']['Password']).')'; else unset($changes['Password']); if ( count($changes) ) { dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id=?', array($uid)); $refreshParent = true; } $view = 'none'; } } if ( $action == 'reset' ) { session_start(); $_SESSION['zmEventResetTime'] = strftime(STRF_FMT_DATETIME_DB); setcookie('zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10); session_write_close(); } ?> ZoneMinder-1.32.2/web/includes/config.php.in0000644000000000000000000001727313365153155017336 0ustar rootroot 0 ) { if ( php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']) ) print( "Warning, overriding installed $localConfigFile file with local copy\n" ); else error_log( "Warning, overriding installed $localConfigFile file with local copy" ); $configFile = $localConfigFile; } # Process name, value pairs from the main config file first $configvals = process_configfile($configFile); # Search for user created config files. If one or more are found then # update our config value array with those values $configSubFolder = ZM_CONFIG_SUBDIR; if ( is_dir($configSubFolder) ) { if ( is_readable($configSubFolder) ) { foreach ( glob("$configSubFolder/*.conf") as $filename ) { //error_log("processing $filename"); $configvals = array_replace($configvals, process_configfile($filename) ); } } else { error_log( "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on $configSubFolder." ); } } else { error_log( "WARNING: ZoneMinder configuration subfolder found but is not a directory. Check $configSubFolder." ); } # Now that our array our finalized, define each key => value # pair in the array as a constant foreach( $configvals as $key => $value) { define( $key, $value ); } // // This section is options normally derived from other options or configuration // define( 'ZMU_PATH', ZM_PATH_BIN.'/zmu' ); // Local path to the ZoneMinder Utility // // If setup supports Video 4 Linux v2 and/or v1 // define( 'ZM_HAS_V4L2', '@ZM_HAS_V4L2@' ); // V4L2 support enabled define( 'ZM_HAS_V4L1', '@ZM_HAS_V4L1@' ); // V4L1 support enabled define( 'ZM_HAS_V4L', '@ZM_HAS_V4L@' ); // V4L support enabled // // If ONVIF support has been built in // define( 'ZM_HAS_ONVIF', '@ZM_HAS_ONVIF@' ); // ONVIF support enabled // // If PCRE dev libraries are installed // define( 'ZM_PCRE', '@ZM_PCRE@' ); // PCRE support enabled // // Alarm states // define( 'STATE_IDLE', 0 ); define( 'STATE_PREALARM', 1 ); define( 'STATE_ALARM', 2 ); define( 'STATE_ALERT', 3 ); define( 'STATE_TAPE', 4 ); // // DVR Control Commands // define( 'MSG_CMD', 1 ); define( 'MSG_DATA_WATCH', 2 ); define( 'MSG_DATA_EVENT', 3 ); define( 'CMD_NONE', 0 ); define( 'CMD_PAUSE', 1 ); define( 'CMD_PLAY', 2 ); define( 'CMD_STOP', 3 ); define( 'CMD_FASTFWD', 4 ); define( 'CMD_SLOWFWD', 5 ); define( 'CMD_SLOWREV', 6 ); define( 'CMD_FASTREV', 7 ); define( 'CMD_ZOOMIN', 8 ); define( 'CMD_ZOOMOUT', 9 ); define( 'CMD_PAN', 10 ); define( 'CMD_SCALE', 11 ); define( 'CMD_PREV', 12 ); define( 'CMD_NEXT', 13 ); define( 'CMD_SEEK', 14 ); define( 'CMD_VARPLAY', 15 ); define( 'CMD_QUIT', 17 ); define( 'CMD_QUERY', 99 ); // // These are miscellaneous options you won't normally need to change // define( 'MAX_EVENTS', 10 ); // The maximum number of events to show in the monitor event listing define( 'RATE_BASE', 100 ); // The additional scaling factor used to help get fractional rates in integer format define( 'SCALE_BASE', 100 ); // The additional scaling factor used to help get fractional scales in integer format // // Date and time formats, not to be modified by language files // define( 'STRF_FMT_DATETIME_DB', '%Y-%m-%d %H:%M:%S' ); // Strftime format for database queries, don't change define( 'MYSQL_FMT_DATETIME_SHORT', '%y/%m/%d %H:%i:%S' ); // MySQL date_format shorter format for dates with time require_once( 'database.php' ); require_once( 'logger.php' ); loadConfig(); Logger::fetch()->initialise(); $GLOBALS['defaultUser'] = array( 'Username' => 'admin', 'Password' => '', 'Language' => '', 'Enabled' => 1, 'Stream' => 'View', 'Events' => 'Edit', 'Control' => 'Edit', 'Monitors' => 'Edit', 'Groups' => 'Edit', 'Devices' => 'Edit', 'System' => 'Edit', 'MaxBandwidth' => '', 'MonitorIds' => false ); function loadConfig( $defineConsts=true ) { global $config; global $configCats; global $dbConn; $config = array(); $configCat = array(); $result = $dbConn->query( 'select * from Config order by Id asc' ); if ( !$result ) echo mysql_error(); $monitors = array(); while( $row = dbFetchNext( $result ) ) { if ( $defineConsts ) define( $row['Name'], $row['Value'] ); $config[$row['Name']] = $row; if ( !($configCat = &$configCats[$row['Category']]) ) { $configCats[$row['Category']] = array(); $configCat = &$configCats[$row['Category']]; } $configCat[$row['Name']] = $row; } //print_r( $config ); //print_r( $configCats ); } // For Human-readability, use ZM_SERVER_HOST or ZM_SERVER_NAME in zm.conf, and convert it here to a ZM_SERVER_ID if ( ! defined('ZM_SERVER_ID') ) { if ( defined('ZM_SERVER_NAME') and ZM_SERVER_NAME ) { $server_id = dbFetchOne('SELECT Id FROM Servers WHERE Name=?', 'Id', array(ZM_SERVER_NAME)); if ( ! $server_id ) { Error('Invalid Multi-Server configration detected. ZM_SERVER_NAME set to ' . ZM_SERVER_NAME . ' in zm.conf, but no corresponding entry found in Servers table.'); } else { define( 'ZM_SERVER_ID', $server_id ); } } else if ( defined('ZM_SERVER_HOST') and ZM_SERVER_HOST ) { $server_id = dbFetchOne('SELECT Id FROM Servers WHERE Name=?', 'Id', array(ZM_SERVER_HOST)); if ( ! $server_id ) { Error('Invalid Multi-Server configration detected. ZM_SERVER_HOST set to ' . ZM_SERVER_HOST . ' in zm.conf, but no corresponding entry found in Servers table.'); } else { define( 'ZM_SERVER_ID', $server_id ); } } } function process_configfile($configFile) { if ( is_readable( $configFile ) ) { $configvals = array(); $cfg = fopen( $configFile, 'r') or Error("Could not open config file: $configFile."); while ( !feof($cfg) ) { $str = fgets( $cfg, 256 ); if ( preg_match( '/^\s*$/', $str )) continue; elseif ( preg_match( '/^\s*#/', $str )) continue; elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $str, $matches )) $configvals[$matches[1]] = $matches[2]; } fclose( $cfg ); return( $configvals ); } else { error_log( "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $configFile." ); return( false ); } } ?> ZoneMinder-1.32.2/web/includes/MontageLayout.php0000644000000000000000000000774213365153155020254 0ustar rootroot null, 'Name' => '', 'Positions' => 0, ); public function __construct( $IdOrRow = NULL ) { if ( $IdOrRow ) { $row = NULL; if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { $row = dbFetchOne( 'SELECT * FROM MontageLayouts WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load MontageLayout record for Id=" . $IdOrRow ); } } else if ( is_array( $IdOrRow ) ) { $row = $IdOrRow; } else { Error("Unknown argument passed to MontageLayout Constructor ($IdOrRow)"); return; } if ( $row ) { foreach ($row as $k => $v) { $this->{$k} = $v; } } else { Error('No row for MontageLayout ' . $IdOrRow ); } } # end if isset($IdOrRow) } // end function __construct public function __call($fn, array $args){ if ( count($args) ) { $this->{$fn} = $args[0]; } if ( array_key_exists($fn, $this) ) { return $this->{$fn}; } else { if ( array_key_exists( $fn, $this->defaults ) ) { return $this->defaults{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning( "Unknown function call MontageLayout->$fn from $file:$line" ); } } } public function set( $data ) { foreach ($data as $k => $v) { if ( is_array( $v ) ) { # perhaps should turn into a comma-separated string $this->{$k} = implode(',',$v); } else if ( is_string( $v ) ) { $this->{$k} = trim( $v ); } else if ( is_integer( $v ) ) { $this->{$k} = $v; } else if ( is_bool( $v ) ) { $this->{$k} = $v; } else { Error( "Unknown type $k => $v of var " . gettype( $v ) ); $this->{$k} = $v; } } } public static function find( $parameters = null, $options = null ) { $filters = array(); $sql = 'SELECT * FROM MontageLayouts '; $values = array(); if ( $parameters ) { $fields = array(); $sql .= 'WHERE '; foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; } else if ( is_array( $value ) ) { $func = function(){return '?';}; $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; $values += $value; } else { $fields[] = $field.'=?'; $values[] = $value; } } $sql .= implode(' AND ', $fields ); } if ( $options and isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } $result = dbQuery($sql, $values); if ( $result ) { $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'MontageLayout'); foreach ( $results as $row => $obj ) { $filters[] = $obj; } } return $filters; } public function save( $new_values = null ) { if ( $new_values ) { foreach ( $new_values as $k=>$v ) { $this->{$k} = $v; } } $fields = array_values( array_filter( array_keys($this->defaults), function($field){return $field != 'Id';} ) ); $values = null; if ( isset($this->{'Id'}) ) { $sql = 'UPDATE MontageLayouts SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ' WHERE Id=?'; $values = array_map( function($field){return $this->{$field};}, $fields ); $values[] = $this->{'Id'}; dbQuery( $sql, $values ); } else { $sql = 'INSERT INTO MontageLayouts ('.implode( ',', $fields ).') VALUES ('.implode(',',array_map( function(){return '?';}, $fields ) ).')'; $values = array_map( function($field){return $this->{$field};}, $fields ); dbQuery( $sql, $values ); global $dbConn; $this->{Id} = $dbConn->lastInsertId(); } } // end function save } // end class MontageLayout ?> ZoneMinder-1.32.2/web/includes/Storage.php0000644000000000000000000001555513365153155017071 0ustar rootroot null, 'Path' => '', 'Name' => '', 'Type' => 'local', 'Url' => '', 'DiskSpace' => null, 'Scheme' => 'Medium', 'ServerId' => 0, 'DoDelete' => 1, ); public function __construct( $IdOrRow = NULL ) { global $storage_cache; $row = NULL; if ( $IdOrRow ) { if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { $row = dbFetchOne('SELECT * FROM Storage WHERE Id=?', NULL, array($IdOrRow)); if ( ! $row ) { Error('Unable to load Storage record for Id=' . $IdOrRow); } } else if ( is_array($IdOrRow) ) { $row = $IdOrRow; } } if ( $row ) { foreach ($row as $k => $v) { $this->{$k} = $v; } $storage_cache[$row['Id']] = $this; } else { $this->{'Name'} = ''; $this->{'Path'} = ''; $this->{'Type'} = 'local'; } } public function Path() { if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { return $this->{'Path'}; } else if ( ! isset($this->{'Id'}) ) { $path = ZM_DIR_EVENTS; if ( $path[0] != '/' ) { $this->{'Path'} = ZM_PATH_WEB.'/'.ZM_DIR_EVENTS; } else { $this->{'Path'} = ZM_DIR_EVENTS; } return $this->{'Path'}; } return $this->{'Name'}; } public function Name() { if ( isset( $this->{'Name'} ) and ( $this->{'Name'} != '' ) ) { return $this->{'Name'}; } else if ( ! isset($this->{'Id'}) ) { return 'Default'; } return $this->{'Name'}; } public function __call( $fn, array $args= NULL ) { if ( count($args) ) { $this->{$fn} = $args[0]; } if ( array_key_exists($fn, $this) ) return $this->{$fn}; if ( array_key_exists( $fn, $this->defaults ) ) return $this->defaults{$fn}; $backTrace = debug_backtrace(); $file = $backTrace[0]['file']; $line = $backTrace[0]['line']; Warning("Unknown function call Storage->$fn from $file:$line"); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning("Unknown function call Storage->$fn from $file:$line"); } public static function find_one( $parameters = null, $options = null ) { global $storage_cache; if ( ( count($parameters) == 1 ) and isset($parameters['Id']) and isset($storage_cache[$parameters['Id']]) ) { return $storage_cache[$parameters['Id']]; } $results = Storage::find($parameters, $options); if ( count($results) > 1 ) { Error("Storage Returned more than 1"); return $results[0]; } else if ( count($results) ) { return $results[0]; } else { return null; } } public static function find( $parameters = null, $options = null ) { $sql = 'SELECT * FROM Storage '; $values = array(); if ( $parameters ) { $fields = array(); $sql .= 'WHERE '; foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; } else if ( is_array($value) ) { $func = function(){return '?';}; $fields[] = $field.' IN ('.implode(',', array_map($func, $value)). ')'; $values += $value; } else { $fields[] = $field.'=?'; $values[] = $value; } } $sql .= implode(' AND ', $fields); } # end if parameters if ( $options ) { if ( isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } # end if options if ( isset($options['limit']) ) { if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { $sql .= ' LIMIT ' . $option['limit']; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Invalid value for limit(".$options['limit'].") passed to Control::find from $file:$line"); return array(); } } # end if limit } # end if options $storage_areas = array(); $result = dbQuery($sql, $values); if ( $result ) { $results = $result->fetchALL(); foreach ( $results as $row ) { $storage_areas[] = new Storage($row); } } return $storage_areas; } # end find() public function disk_usage_percent() { $path = $this->Path(); if ( ! $path ) { Warning('Storage::disk_usage_percent: path is empty'); return 0; } else if ( ! file_exists($path) ) { Warning("Storage::disk_usage_percent: path $path does not exist"); return 0; } $total = $this->disk_total_space(); if ( ! $total ) { Error('disk_total_space returned false for ' . $path ); return 0; } $used = $this->disk_used_space(); $usage = round( ($used / $total) * 100); //Logger::Debug("Used $usage = round( ( $used / $total ) * 100 )"); return $usage; } public function disk_total_space() { if ( !array_key_exists('disk_total_space', $this) ) { $path = $this->Path(); if ( file_exists($path) ) { $this->{'disk_total_space'} = disk_total_space($path); } else { Error("Path $path does not exist."); $this->{'disk_total_space'} = 0; } } return $this->{'disk_total_space'}; } public function disk_used_space() { # This isn't a function like this in php, so we have to add up the space used in each event. if ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) { if ( $this->{'Type'} == 's3fs' ) { $this->{'disk_used_space'} = $this->disk_event_space(); } else { $path = $this->Path(); if ( file_exists($path) ) { $this->{'disk_used_space'} = disk_total_space($path) - disk_free_space($path); } else { Error("Path $path does not exist."); $this->{'disk_used_space'} = 0; } } } return $this->{'disk_used_space'}; } // end function disk_used_space public function event_disk_space() { # This isn't a function like this in php, so we have to add up the space used in each event. if ( (! array_key_exists('DiskSpace', $this)) or (!$this->{'DiskSpace'}) ) { $used = dbFetchOne('SELECT SUM(DiskSpace) AS DiskSpace FROM Events WHERE StorageId=? AND DiskSpace IS NOT NULL', 'DiskSpace', array($this->Id()) ); foreach ( Event::find(array('StorageId'=>$this->Id(), 'DiskSpace'=>null)) as $Event ) { $Event->Storage($this); // Prevent further db hit $used += $Event->DiskSpace(); } $this->{'DiskSpace'} = $used; } return $this->{'DiskSpace'}; } // end function event_disk_space public function Server() { if ( ! array_key_exists('Server',$this) ) { $this->{'Server'}= new Server( $this->{'ServerId'} ); } return $this->{'Server'}; } } ?> ZoneMinder-1.32.2/web/includes/Event.php0000644000000000000000000005631613365153155016546 0ustar rootroot $v) { $this->{$k} = $v; } global $event_cache; $event_cache[$row['Id']] = $this; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error('No row for Event ' . $IdOrRow . " from $file:$line"); } } # end if isset($IdOrRow) } // end function __construct public function Storage( $new = null ) { if ( $new ) { $this->{'Storage'} = $new; } if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) { if ( isset($this->{'StorageId'}) and $this->{'StorageId'} ) $this->{'Storage'} = Storage::find_one(array('Id'=>$this->{'StorageId'})); if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) $this->{'Storage'} = new Storage(NULL); } return $this->{'Storage'}; } public function Monitor() { if ( isset($this->{'MonitorId'}) ) { $Monitor = Monitor::find_one(array('Id'=>$this->{'MonitorId'})); if ( $Monitor ) return $Monitor; } return new Monitor(); } public function __call( $fn, array $args){ if ( count( $args ) ) { $this->{$fn} = $args[0]; } if ( array_key_exists( $fn, $this ) ) { return $this->{$fn}; $backTrace = debug_backtrace(); $file = $backTrace[0]['file']; $line = $backTrace[0]['line']; Warning("Unknown function call Event->$fn from $file:$line"); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning("Unknown function call Event->$fn from $file:$line"); Warning(print_r( $this, true )); } } public function Time() { if ( ! isset($this->{'Time'}) ) { $this->{'Time'} = strtotime($this->{'StartTime'}); } return $this->{'Time'}; } public function Path() { $Storage = $this->Storage(); return $Storage->Path().'/'.$this->Relative_Path(); } public function Relative_Path() { $event_path = ''; if ( $this->{'Scheme'} == 'Deep' ) { $event_path = $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/%H/%M/%S', $this->Time()) ; } else if ( $this->{'Scheme'} == 'Medium' ) { $event_path = $this->{'MonitorId'} .'/'.strftime( '%Y-%m-%d', $this->Time() ) . '/'.$this->{'Id'}; } else { $event_path = $this->{'MonitorId'} .'/'.$this->{'Id'}; } return $event_path; } // end function Relative_Path() public function Link_Path() { if ( $this->{'Scheme'} == 'Deep' ) { return $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/.', $this->Time()).$this->{'Id'}; } Error('Calling Link_Path when not using deep storage'); return ''; } public function delete() { # This wouldn't work with foreign keys dbQuery( 'DELETE FROM Events WHERE Id = ?', array($this->{'Id'}) ); if ( !ZM_OPT_FAST_DELETE ) { dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}) ); dbQuery( 'DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}) ); if ( $this->{'Scheme'} == 'Deep' ) { # Assumption: All events have a start time $start_date = date_parse( $this->{'StartTime'} ); if ( ! $start_date ) { Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.' ); return; } $start_date['year'] = $start_date['year'] % 100; # So this is because ZM creates a link under the day pointing to the time that the event happened. $link_path = $this->Link_Path(); if ( ! $link_path ) { Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.' ); return; } $Storage = $this->Storage(); $eventlink_path = $Storage->Path().'/'.$link_path; if ( $id_files = glob( $eventlink_path ) ) { if ( ! $eventPath = readlink($id_files[0]) ) { Error("Unable to read link at $id_files[0]"); return; } # I know we are using arrays here, but really there can only ever be 1 in the array $eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] ); deletePath( $eventPath ); deletePath( $id_files[0] ); $pathParts = explode( '/', $eventPath ); for ( $i = count($pathParts)-1; $i >= 2; $i-- ) { $deletePath = join( '/', array_slice( $pathParts, 0, $i ) ); if ( !glob( $deletePath."/*" ) ) { deletePath( $deletePath ); } } } else { Warning( "Found no event files under $eventlink_path" ); } # end if found files } else { $eventPath = $this->Path(); deletePath( $eventPath ); } # USE_DEEP_STORAGE OR NOT } # ! ZM_OPT_FAST_DELETE } # end Event->delete public function getStreamSrc( $args=array(), $querySep='&' ) { $streamSrc = ZM_BASE_PROTOCOL.'://'; if ( $this->Storage()->ServerId() ) { $Server = $this->Storage()->Server(); $streamSrc .= $Server->Hostname(); if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } } else if ( $this->Monitor()->ServerId() ) { # Assume that the server that recorded it has it $Server = $this->Monitor()->Server(); $streamSrc .= $Server->Hostname(); if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } } else if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= $_SERVER['SERVER_NAME'].':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } else { $streamSrc .= $_SERVER['HTTP_HOST']; } if ( $this->{'DefaultVideo'} and $args['mode'] != 'jpeg' ) { $streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php'; $args['eid'] = $this->{'Id'}; $args['view'] = 'view_video'; } else { $streamSrc .= ZM_PATH_ZMS; $args['source'] = 'event'; $args['event'] = $this->{'Id'}; if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) { $args['connkey'] = $GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { $args['rand'] = time(); } } if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $args['user'] = $_SESSION['username']; $args['pass'] = $_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $args['user'] = $_SESSION['username']; } } $streamSrc .= '?'.http_build_query($args,'', $querySep); return $streamSrc; } // end function getStreamSrc function DiskSpace( $new='' ) { if ( is_null($new) or ( $new != '' ) ) { $this->{'DiskSpace'} = $new; } if ( (!array_key_exists('DiskSpace',$this)) or (null === $this->{'DiskSpace'}) ) { $this->{'DiskSpace'} = folder_size($this->Path()); dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'})); } return $this->{'DiskSpace'}; } function createListThumbnail( $overwrite=false ) { # The idea here is that we don't really want to use the analysis jpeg as the thumbnail. # The snapshot image will be generated during capturing if ( file_exists($this->Path().'/snapshot.jpg') ) { Logger::Debug("snapshot exists"); $frame = null; } else { # Load the frame with the highest score to use as a thumbnail if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) { Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} ); // FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame. return false; } } $imageData = $this->getImageSrc($frame, $scale, false, $overwrite); if ( ! $imageData ) { return false; } $thumbData = $frame; $thumbData['Path'] = $imageData['thumbPath']; $thumbData['Width'] = $this->ThumbnailWidth(); $thumbData['Height'] = $this->ThumbnailHeight(); $thumbData['url'] = '?view=image&eid='.$this->Id().'&fid='.$imageData['FrameId'].'&width='.$thumbData['Width'].'&height='.$thumbData['Height']; return $thumbData; } // end function createListThumbnail function ThumbnailWidth( ) { if ( ! ( array_key_exists('ThumbnailWidth', $this) ) ) { if ( ZM_WEB_LIST_THUMB_WIDTH ) { $this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'}; $this->{'ThumbnailHeight'} = reScale( $this->{'Height'}, $scale ); } elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) { $this->{'ThumbnailHeight'} = ZM_WEB_LIST_THUMB_HEIGHT; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$this->{'Height'}; $this->{'ThumbnailWidth'} = reScale( $this->{'Width'}, $scale ); } else { Fatal( "No thumbnail width or height specified, please check in Options->Web" ); } } return $this->{'ThumbnailWidth'}; } // end function ThumbnailWidth function ThumbnailHeight( ) { if ( ! ( array_key_exists('ThumbnailHeight', $this) ) ) { if ( ZM_WEB_LIST_THUMB_WIDTH ) { $this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'}; $this->{'ThumbnailHeight'} = reScale( $this->{'Height'}, $scale ); } elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) { $this->{'ThumbnailHeight'} = ZM_WEB_LIST_THUMB_HEIGHT; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$this->{'Height'}; $this->{'ThumbnailWidth'} = reScale( $this->{'Width'}, $scale ); } else { Fatal( "No thumbnail width or height specified, please check in Options->Web" ); } } return $this->{'ThumbnailHeight'}; } // end function ThumbnailHeight function getThumbnailSrc( $args=array(), $querySep='&' ) { # The thumbnail is theoretically the image with the most motion. # We always store at least 1 image when capturing $streamSrc = ZM_BASE_PROTOCOL.'://'; if ( $this->Storage()->ServerId() ) { $Server = $this->Storage()->Server(); $streamSrc .= $Server->Hostname(); if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } } else if ( $this->Monitor()->ServerId() ) { $Server = $this->Monitor()->Server(); $streamSrc .= $Server->Hostname(); if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } } else if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= $_SERVER['SERVER_NAME'].':'.(ZM_MIN_STREAMING_PORT+$this->{'MonitorId'}); } else { $streamSrc .= $_SERVER['HTTP_HOST']; } $streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php'; $args['eid'] = $this->{'Id'}; $args['fid'] = 'snapshot'; $args['view'] = 'image'; $args['width'] = $this->ThumbnailWidth(); $args['height'] = $this->ThumbnailHeight(); if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $args['user'] = $_SESSION['username']; $args['pass'] = $_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $args['user'] = $_SESSION['username']; } } return $streamSrc.'?'.http_build_query($args,'', $querySep); } // end function getThumbnailSrc // frame is an array representing the db row for a frame. function getImageSrc($frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false) { $Storage = $this->Storage(); $Event = $this; $eventPath = $Event->Path(); if ( $frame and ! is_array($frame) ) { # Must be an Id Logger::Debug("Assuming that $frame is an Id"); $frame = array( 'FrameId'=>$frame, 'Type'=>'' ); } if ( ( ! $frame ) and file_exists($eventPath.'/snapshot.jpg') ) { # No frame specified, so look for a snapshot to use $captImage = 'snapshot.jpg'; Logger::Debug("Frame not specified, using snapshot"); $frame = array('FrameId'=>'snapshot', 'Type'=>''); } else { $captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyze.jpg', $frame['FrameId'] ); if ( ! file_exists( $eventPath.'/'.$captImage ) ) { $captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-capture.jpg', $frame['FrameId'] ); if ( ! file_exists( $eventPath.'/'.$captImage ) ) { # Generate the frame JPG if ( $Event->DefaultVideo() ) { $videoPath = $eventPath.'/'.$Event->DefaultVideo(); if ( ! file_exists( $videoPath ) ) { Error("Event claims to have a video file, but it does not seem to exist at $videoPath" ); return ''; } #$command ='ffmpeg -v 0 -i '.$videoPath.' -vf "select=gte(n\\,'.$frame['FrameId'].'),setpts=PTS-STARTPTS" '.$eventPath.'/'.$captImage; $command ='ffmpeg -ss '. $frame['Delta'] .' -i '.$videoPath.' -frames:v 1 '.$eventPath.'/'.$captImage; Logger::Debug( "Running $command" ); $output = array(); $retval = 0; exec( $command, $output, $retval ); Logger::Debug("Retval: $retval, output: " . implode("\n", $output)); } else { Error("Can't create frame images from video because there is no video file for event ".$Event->Id().' at ' .$Event->Path() ); } } // end if capture file exists } // end if analyze file exists } $captPath = $eventPath.'/'.$captImage; if ( ! file_exists($captPath) ) { Error( "Capture file does not exist at $captPath" ); } $thumbCaptPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$captImage; //echo "CI:$captImage, CP:$captPath, TCP:$thumbCaptPath
    "; $analImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId'] ); $analPath = $eventPath.'/'.$analImage; $thumbAnalPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$analImage; //echo "AI:$analImage, AP:$analPath, TAP:$thumbAnalPath
    "; $alarmFrame = $frame['Type']=='Alarm'; $hasAnalImage = $alarmFrame && file_exists( $analPath ) && filesize( $analPath ); $isAnalImage = $hasAnalImage && !$captureOnly; if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists( 'imagecreatefromjpeg' ) ) { $imagePath = $thumbPath = $isAnalImage?$analPath:$captPath; $imageFile = $imagePath; $thumbFile = $thumbPath; } else { if ( version_compare( phpversion(), '4.3.10', '>=') ) $fraction = sprintf( '%.3F', $scale/SCALE_BASE ); else $fraction = sprintf( '%.3f', $scale/SCALE_BASE ); $scale = (int)round( $scale ); $thumbCaptPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbCaptPath ); $thumbAnalPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbAnalPath ); if ( $isAnalImage ) { $imagePath = $analPath; $thumbPath = $thumbAnalPath; } else { $imagePath = $captPath; $thumbPath = $thumbCaptPath; } $thumbFile = $thumbPath; if ( $overwrite || ! file_exists( $thumbFile ) || ! filesize( $thumbFile ) ) { // Get new dimensions list( $imageWidth, $imageHeight ) = getimagesize( $imagePath ); $thumbWidth = $imageWidth * $fraction; $thumbHeight = $imageHeight * $fraction; // Resample $thumbImage = imagecreatetruecolor( $thumbWidth, $thumbHeight ); $image = imagecreatefromjpeg( $imagePath ); imagecopyresampled( $thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight ); if ( !imagejpeg( $thumbImage, $thumbPath ) ) Error( "Can't create thumbnail '$thumbPath'" ); } } # Create thumbnails $imageData = array( 'eventPath' => $eventPath, 'imagePath' => $imagePath, 'thumbPath' => $thumbPath, 'imageFile' => $imagePath, 'thumbFile' => $thumbFile, 'imageClass' => $alarmFrame?'alarm':'normal', 'isAnalImage' => $isAnalImage, 'hasAnalImage' => $hasAnalImage, 'FrameId' => $frame['FrameId'], ); return $imageData; } public static function find_one( $parameters = null, $options = null ) { global $event_cache; if ( ( count($parameters) == 1 ) and isset($parameters['Id']) and isset($event_cache[$parameters['Id']]) ) { return $event_cache[$parameters['Id']]; } $results = Event::find( $parameters, $options ); if ( count($results) > 1 ) { Error("Event Returned more than 1"); return $results[0]; } else if ( count($results) ) { return $results[0]; } else { return null; } } public static function find( $parameters = null, $options = null ) { $sql = 'SELECT * FROM Events '; $values = array(); if ( $parameters ) { $fields = array(); $sql .= 'WHERE '; foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; } else if ( is_array( $value ) ) { $func = function(){return '?';}; $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; $values += $value; } else { $fields[] = $field.'=?'; $values[] = $value; } } $sql .= implode(' AND ', $fields ); } if ( $options ) { if ( isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } if ( isset($options['limit']) ) { if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { $sql .= ' LIMIT ' . $options['limit']; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Invalid value for limit(".$options['limit'].") passed to Event::find from $file:$line"); return array(); } } } $filters = array(); $result = dbQuery($sql, $values); $results = $result->fetchALL(); foreach ( $results as $row ) { $filters[] = new Event($row); } return $filters; } public function save( ) { $sql = 'UPDATE Events SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $this->fields ) ) . ' WHERE Id=?'; $values = array_map( function($field){return $this->{$field};}, $this->fields ); $values[] = $this->{'Id'}; dbQuery( $sql, $values ); } public function link_to($text=null) { if ( !$text ) $text = $this->{'Id'}; return ''.$text.''; } public function file_exists() { if ( file_exists( $this->Path().'/'.$this->DefaultVideo() ) ) { return true; } $Storage= $this->Storage(); $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); if ( $Server->Id() != ZM_SERVER_ID ) { $url = $Server->Url() . '/zm/api/events/'.$this->{'Id'}.'.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $url = '?user='.$_SESSION['username']; $url = '?pass='.$_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $url = '?user='.$_SESSION['username']; } } Logger::Debug("sending command to $url"); // use key 'http' even if you send the request to https://... $options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'GET', 'content' => '' ) ); $context = stream_context_create($options); try { $result = file_get_contents($url, false, $context); if ($result === FALSE) { /* Handle error */ Error("Error restarting zmc using $url"); } $event_data = json_decode($result,true); Logger::Debug(print_r($event_data['event']['Event'],1)); return $event_data['event']['Event']['fileExists']; } catch ( Exception $e ) { Error("Except $e thrown trying to get event data"); } } # end if not local return false; } # end public function file_exists() public function file_size() { if ( file_exists($this->Path().'/'.$this->DefaultVideo()) ) { return filesize($this->Path().'/'.$this->DefaultVideo()); } $Storage= $this->Storage(); $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); if ( $Server->Id() != ZM_SERVER_ID ) { $url = $Server->Url() . '/zm/api/events/'.$this->{'Id'}.'.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $url = '?user='.$_SESSION['username']; $url = '?pass='.$_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $url = '?user='.$_SESSION['username']; } } Logger::Debug("sending command to $url"); // use key 'http' even if you send the request to https://... $options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'GET', 'content' => '' ) ); $context = stream_context_create($options); try { $result = file_get_contents($url, false, $context); if ($result === FALSE) { /* Handle error */ Error("Error restarting zmc using $url"); } $event_data = json_decode($result,true); Logger::Debug(print_r($event_data['event']['Event'],1)); return $event_data['event']['Event']['fileSize']; } catch ( Exception $e ) { Error("Except $e thrown trying to get event data"); } } # end if not local return 0; } # end public function file_size() } # end class ?> ZoneMinder-1.32.2/web/includes/database.php0000644000000000000000000002573113365153155017226 0ustar rootroot ZM_DB_SSL_CA_CERT, PDO::MYSQL_ATTR_SSL_KEY => ZM_DB_SSL_CLIENT_KEY, PDO::MYSQL_ATTR_SSL_CERT => ZM_DB_SSL_CLIENT_CERT, ); $dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS, $dbOptions ); } else { $dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS ); } $dbConn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $ex ) { echo 'Unable to connect to ZM db.' . $ex->getMessage(); error_log('Unable to connect to ZM DB ' . $ex->getMessage() ); $dbConn = null; } } dbConnect(); function dbDisconnect() { global $dbConn; $dbConn = null; } function dbLogOff() { global $dbLogLevel; $dbLogLevel = DB_LOG_OFF; } function dbLogOn() { global $dbLogLevel; $dbLogLevel = DB_LOG_ONLY; } function dbLogDebug() { global $dbLogLevel; $dbLogLevel = DB_LOG_DEBUG; } function dbDebug() { dbLogDebug(); } function dbLog( $sql, $update=false ) { global $dbLogLevel; $noExecute = $update && ($dbLogLevel >= DB_LOG_DEBUG); if ( $dbLogLevel > DB_LOG_OFF ) Logger::Debug( "SQL-LOG: $sql".($noExecute?" (not executed)":"") ); return( $noExecute ); } function dbError( $sql ) { Error( "SQL-ERR '".$dbConn->errorInfo()."', statement was '".$sql."'" ); } function dbEscape( $string ) { global $dbConn; if ( version_compare( phpversion(), '4.3.0', '<') ) if ( get_magic_quotes_gpc() ) return( $dbConn->quote( stripslashes( $string ) ) ); else return( $dbConn->quote( $string ) ); else if ( get_magic_quotes_gpc() ) return( $dbConn->quote( stripslashes( $string ) ) ); else return( $dbConn->quote( $string ) ); } function dbQuery( $sql, $params=NULL ) { global $dbConn; if ( dbLog( $sql, true ) ) return; $result = NULL; try { if ( isset($params) ) { if ( ! $result = $dbConn->prepare( $sql ) ) { Error("SQL: Error preparing $sql: " . $pdo->errorInfo); return NULL; } if ( ! $result->execute( $params ) ) { Error("SQL: Error executing $sql: " . implode(',', $result->errorInfo() ) ); return NULL; } } else { if ( defined('ZM_DB_DEBUG') ) { Logger::Debug("SQL: $sql values:" . ($params?implode(',',$params):'') ); } $result = $dbConn->query($sql); } if ( defined('ZM_DB_DEBUG') ) { if ( $params ) Logger::Debug("SQL: $sql" . implode(',',$params) . ' rows: '.$result->rowCount() ); else Logger::Debug("SQL: $sql: rows:" . $result->rowCount() ); } } catch(PDOException $e) { Error( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."' params:" . ($params?implode(',',$params):'') ); return NULL; } return $result; } function dbFetchOne( $sql, $col=false, $params=NULL ) { $result = dbQuery( $sql, $params ); if ( ! $result ) { Error( "SQL-ERR dbFetchOne no result, statement was '".$sql."'" . ( $params ? 'params: ' . join(',',$params) : '' ) ); return false; } if ( ! $result->rowCount() ) { # No rows is not an error return false; } if ( $result && $dbRow = $result->fetch(PDO::FETCH_ASSOC) ) { if ( $col ) { if ( ! array_key_exists($col, $dbRow) ) { Warning("$col does not exist in the returned row " . print_r($dbRow, true)); } return $dbRow[$col]; } return $dbRow; } return false; } function dbFetchAll( $sql, $col=false, $params=NULL ) { $result = dbQuery( $sql, $params ); if ( ! $result ) { Error( "SQL-ERR dbFetchAll no result, statement was '".$sql."'" . ( $params ? 'params: ' .join(',', $params) : '' ) ); return false; } $dbRows = array(); while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) $dbRows[] = $col?$dbRow[$col]:$dbRow; return $dbRows; } function dbFetchAssoc( $sql, $indexCol, $dataCol=false ) { $result = dbQuery( $sql ); $dbRows = array(); while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) $dbRows[$dbRow[$indexCol]] = $dataCol?$dbRow[$dataCol]:$dbRow; return( $dbRows ); } function dbFetch( $sql, $col=false ) { return( dbFetchAll( $sql, $col ) ); } function dbFetchNext( $result, $col=false ) { if ( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) return( $col?$dbRow[$col]:$dbRow ); return( false ); } function dbNumRows( $sql ) { $result = dbQuery( $sql ); return( $result->rowCount() ); } function dbInsertId() { global $dbConn; return( $dbConn->lastInsertId() ); } function getEnumValues( $table, $column ) { $row = dbFetchOne( "describe $table $column" ); preg_match_all( "/'([^']+)'/", $row['Type'], $matches ); return( $matches[1] ); } function getSetValues( $table, $column ) { return( getEnumValues( $table, $column ) ); } function getUniqueValues( $table, $column, $asString=1 ) { $values = array(); $sql = "select distinct $column from $table where (not isnull($column) and $column != '') order by $column"; foreach( dbFetchAll( $sql ) as $row ) { if ( $asString ) $values[$row[$column]] = $row[$column]; else $values[] = $row[$column]; } return( $values ); } function getTableColumns( $table, $asString=1 ) { $columns = array(); $sql = "describe $table"; foreach( dbFetchAll( $sql ) as $row ) { if ( $asString ) $columns[$row['Field']] = $row['Type']; else $columns[] = $row['Type']; } return( $columns ); } function getTableAutoInc( $table ) { $row = dbFetchOne( 'show table status where Name=?', NULL, array($table) ); return( $row['Auto_increment'] ); } function getTableDescription( $table, $asString=1 ) { $columns = array(); foreach( dbFetchAll( "describe $table" ) as $row ) { $desc = array( 'name' => $row['Field'], 'required' => ($row['Null']=='NO')?true:false, 'default' => $row['Default'], 'db' => $row, ); if ( preg_match( "/^varchar\((\d+)\)$/", $row['Type'], $matches ) ) { $desc['type'] = 'text'; $desc['typeAttrib'] = 'varchar'; $desc['maxLength'] = $matches[1]; } elseif ( preg_match( "/^(\w+)?text$/", $row['Type'], $matches ) ) { $desc['type'] = 'text'; if (!empty($matches[1]) ) $desc['typeAttrib'] = $matches[1]; switch ( $matches[1] ) { case 'tiny' : $desc['maxLength'] = 255; break; case 'medium' : $desc['maxLength'] = 32768; break; case '' : case 'big' : //$desc['minLength'] = -128; break; default : Error( "Unexpected text qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); break; } } elseif ( preg_match( "/^(enum|set)\((.*)\)$/", $row['Type'], $matches ) ) { $desc['type'] = 'text'; $desc['typeAttrib'] = $matches[1]; preg_match_all( "/'([^']+)'/", $matches[2], $matches ); $desc['values'] = $matches[1]; } elseif ( preg_match( "/^(\w+)?int\(\d+\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) { $desc['type'] = 'integer'; switch ( $matches[1] ) { case 'tiny' : $desc['minValue'] = -128; $desc['maxValue'] = 127; break; case 'small' : $desc['minValue'] = -32768; $desc['maxValue'] = 32767; break; case 'medium' : $desc['minValue'] = -8388608; $desc['maxValue'] = 8388607; break; case '' : $desc['minValue'] = -2147483648; $desc['maxValue'] = 2147483647; break; case 'big' : //$desc['minValue'] = -128; //$desc['maxValue'] = 127; break; default : Error( "Unexpected integer qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); break; } if ( !empty($matches[1]) ) $desc['typeAttrib'] = $matches[1]; if ( $desc['unsigned'] = ( isset($matches[2]) && $matches[2] == 'unsigned' ) ) { $desc['maxValue'] += (-$desc['minValue']); $desc['minValue'] = 0; } } elseif ( preg_match( "/^(?:decimal|numeric)\((\d+)(?:,(\d+))?\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) { $desc['type'] = 'fixed'; $desc['range'] = $matches[1]; if ( isset($matches[2]) ) $desc['precision'] = $matches[2]; else $desc['precision'] = 0; $desc['unsigned'] = ( isset($matches[3]) && $matches[3] == 'unsigned' ); } elseif ( preg_match( "/^(datetime|timestamp|date|time)$/", $row['Type'], $matches ) ) { $desc['type'] = 'datetime'; switch ( $desc['typeAttrib'] = $matches[1] ) { case 'datetime' : case 'timestamp' : $desc['hasDate'] = true; $desc['hasTime'] = true; break; case 'date' : $desc['hasDate'] = true; $desc['hasTime'] = false; break; case 'time' : $desc['hasDate'] = false; $desc['hasTime'] = true; break; } } else { Error( "Can't parse database type '".$row['Type']."' found for field '".$row['Field']."' in table '".$table."'" ); } if ( $asString ) $columns[$row['Field']] = $desc; else $columns[] = $desc; } return( $columns ); } function dbFetchMonitor( $mid ) { return( dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($mid) ) ); } function dbFetchGroup( $gid ) { return( dbFetchOne( 'select * from Groups where Id = ?', NULL, array($gid) ) ); } ?> ZoneMinder-1.32.2/web/includes/Monitor.php0000644000000000000000000004324713365153155017113 0ustar rootroot null, 'Name' => '', 'StorageId' => 0, 'ServerId' => 0, 'Type' => 'Ffmpeg', 'Function' => 'None', 'Enabled' => 1, 'LinkedMonitors' => null, 'Width' => null, 'Height' => null, 'Orientation' => null, 'AnalysisFPSLimit' => null, 'ZoneCount' => 0, 'Triggers' => null, 'MaxFPS' => null, 'AlarmMaxFPS' => null, 'Refresh' => null, ); private $status_fields = array( 'AnalysisFPS' => null, 'CaptureFPS' => null, 'CaptureBandwidth' => null, ); private $control_fields = array( 'Name' => '', 'Type' => 'Local', 'Protocol' => NULL, 'CanWake' => '0', 'CanSleep' => '0', 'CanReset' => '0', 'CanZoom' => '0', 'CanAutoZoom' => '0', 'CanZoomAbs' => '0', 'CanZoomRel' => '0', 'CanZoomCon' => '0', 'MinZoomRange' => NULL, 'MaxZoomRange' => NULL, 'MinZoomStep' => NULL, 'MaxZoomStep' => NULL, 'HasZoomSpeed' => '0', 'MinZoomSpeed' => NULL, 'MaxZoomSpeed' => NULL, 'CanFocus' => '0', 'CanAutoFocus' => '0', 'CanFocusAbs' => '0', 'CanFocusRel' => '0', 'CanFocusCon' => '0', 'MinFocusRange' => NULL, 'MaxFocusRange' => NULL, 'MinFocusStep' => NULL, 'MaxFocusStep' => NULL, 'HasFocusSpeed' => '0', 'MinFocusSpeed' => NULL, 'MaxFocusSpeed' => NULL, 'CanIris' => '0', 'CanAutoIris' => '0', 'CanIrisAbs' => '0', 'CanIrisRel' => '0', 'CanIrisCon' => '0', 'MinIrisRange' => NULL, 'MaxIrisRange' => NULL, 'MinIrisStep' => NULL, 'MaxIrisStep' => NULL, 'HasIrisSpeed' => '0', 'MinIrisSpeed' => NULL, 'MaxIrisSpeed' => NULL, 'CanGain' => '0', 'CanAutoGain' => '0', 'CanGainAbs' => '0', 'CanGainRel' => '0', 'CanGainCon' => '0', 'MinGainRange' => NULL, 'MaxGainRange' => NULL, 'MinGainStep' => NULL, 'MaxGainStep' => NULL, 'HasGainSpeed' => '0', 'MinGainSpeed' => NULL, 'MaxGainSpeed' => NULL, 'CanWhite' => '0', 'CanAutoWhite' => '0', 'CanWhiteAbs' => '0', 'CanWhiteRel' => '0', 'CanWhiteCon' => '0', 'MinWhiteRange' => NULL, 'MaxWhiteRange' => NULL, 'MinWhiteStep' => NULL, 'MaxWhiteStep' => NULL, 'HasWhiteSpeed' => '0', 'MinWhiteSpeed' => NULL, 'MaxWhiteSpeed' => NULL, 'HasPresets' => '0', 'NumPresets' => '0', 'HasHomePreset' => '0', 'CanSetPresets' => '0', 'CanMove' => '0', 'CanMoveDiag' => '0', 'CanMoveMap' => '0', 'CanMoveAbs' => '0', 'CanMoveRel' => '0', 'CanMoveCon' => '0', 'CanPan' => '0', 'MinPanRange' => NULL, 'MaxPanRange' => NULL, 'MinPanStep' => NULL, 'MaxPanStep' => NULL, 'HasPanSpeed' => '0', 'MinPanSpeed' => NULL, 'MaxPanSpeed' => NULL, 'HasTurboPan' => '0', 'TurboPanSpeed' => NULL, 'CanTilt' => '0', 'MinTiltRange' => NULL, 'MaxTiltRange' => NULL, 'MinTiltStep' => NULL, 'MaxTiltStep' => NULL, 'HasTiltSpeed' => '0', 'MinTiltSpeed' => NULL, 'MaxTiltSpeed' => NULL, 'HasTurboTilt' => '0', 'TurboTiltSpeed' => NULL, 'CanAutoScan' => '0', 'NumScanPaths' => '0', ); public function __construct( $IdOrRow = NULL ) { if ( $IdOrRow ) { $row = NULL; if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { $row = dbFetchOne('SELECT * FROM Monitors WHERE Id=?', NULL, array($IdOrRow)); if ( ! $row ) { Error("Unable to load Monitor record for Id=" . $IdOrRow); } } elseif ( is_array($IdOrRow) ) { $row = $IdOrRow; } else { Error("Unknown argument passed to Monitor Constructor ($IdOrRow)"); return; } if ( $row ) { foreach ($row as $k => $v) { $this->{$k} = $v; } if ( $this->{'Controllable'} ) { $s = dbFetchOne('SELECT * FROM Controls WHERE Id=?', NULL, array($this->{'ControlId'}) ); if ( $s ) { foreach ($s as $k => $v) { if ( $k == 'Id' ) { continue; # The reason for these is that the name overlaps Monitor fields. } else if ( $k == 'Protocol' ) { $this->{'ControlProtocol'} = $v; } else if ( $k == 'Name' ) { $this->{'ControlName'} = $v; } else if ( $k == 'Type' ) { $this->{'ControlType'} = $v; } else { $this->{$k} = $v; } } } else { Warning('No Controls found for monitor '.$this->{'Id'} . ' ' . $this->{'Name'}.' althrough it is marked as controllable'); } } global $monitor_cache; $monitor_cache[$row['Id']] = $this; } else { Error('No row for Monitor ' . $IdOrRow); } } # end if isset($IdOrRow) } // end function __construct public function Server() { return new Server($this->{'ServerId'}); } public function __call($fn, array $args){ if ( count($args) ) { $this->{$fn} = $args[0]; } if ( array_key_exists($fn, $this) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); } else { if ( array_key_exists($fn, $this->control_fields) ) { return $this->control_fields{$fn}; } else if ( array_key_exists( $fn, $this->defaults ) ) { return $this->defaults{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning( "Unknown function call Monitor->$fn from $file:$line" ); } } } public function getStreamSrc( $args, $querySep='&' ) { $streamSrc = ZM_BASE_PROTOCOL.'://'; if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { $Server = new Server( $this->{'ServerId'} ); $streamSrc .= $Server->Hostname(); if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= ':'.(ZM_MIN_STREAMING_PORT+$this->{'Id'}); } } else if ( ZM_MIN_STREAMING_PORT ) { $streamSrc .= $_SERVER['SERVER_NAME'].':'.(ZM_MIN_STREAMING_PORT+$this->{'Id'}); } else { $streamSrc .= $_SERVER['HTTP_HOST']; } $streamSrc .= ZM_PATH_ZMS; $args['monitor'] = $this->{'Id'}; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $args['user'] = $_SESSION['username']; $args['pass'] = $_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $args['user'] = $_SESSION['username']; } } if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) { $args['connkey'] = $GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { $args['rand'] = time(); } $streamSrc .= '?'.http_build_query( $args,'', $querySep ); return( $streamSrc ); } // end function getStreamSrc public function Width($new = null) { if ( $new ) $this->{'Width'} = $new; $field = ( $this->Orientation() == '90' or $this->Orientation() == '270' ) ? 'Height' : 'Width'; if ( array_key_exists($field, $this) ) return $this->{$field}; return $this->defaults{$field}; } // end function Width public function Height($new=null) { if ( $new ) $this->{'Height'} = $new; $field = ( $this->Orientation() == '90' or $this->Orientation() == '270' ) ? 'Width' : 'Height'; if ( array_key_exists($field, $this) ) return $this->{$field}; return $this->defaults{$field}; } // end function Height public function set($data) { foreach ($data as $k => $v) { if ( method_exists($this, $k) ) { $this->{$k}($v); } else { if ( is_array( $v ) ) { # perhaps should turn into a comma-separated string $this->{$k} = implode(',',$v); } else if ( is_string( $v ) ) { $this->{$k} = trim( $v ); } else if ( is_integer( $v ) ) { $this->{$k} = $v; } else if ( is_bool( $v ) ) { $this->{$k} = $v; } else { Error( "Unknown type $k => $v of var " . gettype( $v ) ); $this->{$k} = $v; } } # end if method_exists } # end foreach $data as $k=>$v } public static function find( $parameters = null, $options = null ) { $sql = 'SELECT * FROM Monitors '; $values = array(); if ( $parameters ) { $fields = array(); $sql .= 'WHERE '; foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; } else if ( is_array($value) ) { $func = function(){return '?';}; $fields[] = $field.' IN ('.implode(',', array_map($func, $value)). ')'; $values += $value; } else { $fields[] = $field.'=?'; $values[] = $value; } } $sql .= implode(' AND ', $fields); } if ( $options ) { if ( isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } if ( isset($options['limit']) ) { if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { $sql .= ' LIMIT ' . $options['limit']; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Invalid value for limit(".$options['limit'].") passed to Control::find from $file:$line"); return array(); } } } $monitors = array(); $result = dbQuery($sql, $values); $results = $result->fetchALL(); foreach ( $results as $row ) { $monitors[] = new Monitor($row); } return $monitors; } # end find public static function find_one( $parameters = array() ) { global $monitor_cache; if ( ( count($parameters) == 1 ) and isset($parameters['Id']) and isset($monitor_cache[$parameters['Id']]) ) { return $monitor_cache[$parameters['Id']]; } $results = Monitor::find( $parameters, array('limit'=>1) ); if ( ! sizeof($results) ) { return; } return $results[0]; } # end find_one public function save($new_values = null) { if ( $new_values ) { foreach ( $new_values as $k=>$v ) { $this->{$k} = $v; } } $fields = array_keys($this->defaults); $sql = 'UPDATE Monitors SET '.implode(', ', array_map(function($field) {return $field.'=?';}, $fields )) . ' WHERE Id=?'; $values = array_map(function($field){return $this->{$field};}, $fields); $values[] = $this->{'Id'}; dbQuery($sql, $values); } // end function save function zmcControl( $mode=false ) { if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ( $this->{'Type'} == 'Local' ) { $zmcArgs = '-d '.$this->{'Device'}; } else { $zmcArgs = '-m '.$this->{'Id'}; } if ( $mode == 'stop' ) { daemonControl('stop', 'zmc', $zmcArgs); } else { if ( $mode == 'restart' ) { daemonControl('stop', 'zmc', $zmcArgs); } if ( $this->{'Function'} != 'None' ) { daemonControl('start', 'zmc', $zmcArgs); } } } else if ( $this->ServerId() ) { $Server = $this->Server(); $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/'.$this->{'Id'}.'.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $url = '?user='.$_SESSION['username']; $url = '?pass='.$_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $url = '?user='.$_SESSION['username']; } } Logger::Debug("sending command to $url"); $data = array('Monitor[Function]' => $this->{'Function'} ); // use key 'http' even if you send the request to https://... $options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'POST', 'content' => http_build_query($data) ) ); $context = stream_context_create($options); try { $result = file_get_contents($url, false, $context); if ($result === FALSE) { /* Handle error */ Error("Error restarting zmc using $url"); } } catch ( Exception $e ) { Error("Except $e thrown trying to restart zmc"); } } else { Error("Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor."); } } // end function zmcControl function zmaControl( $mode=false ) { if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ( $this->{'Function'} == 'None' || $this->{'Function'} == 'Monitor' || $mode == 'stop' ) { if ( ZM_OPT_CONTROL ) { daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'}); } daemonControl('stop', 'zma', '-m '.$this->{'Id'}); } else { if ( $mode == 'restart' ) { if ( ZM_OPT_CONTROL ) { daemonControl( 'stop', 'zmtrack.pl', '-m '.$this->{'Id'} ); } daemonControl( 'stop', 'zma', '-m '.$this->{'Id'} ); } daemonControl( 'start', 'zma', '-m '.$this->{'Id'} ); if ( ZM_OPT_CONTROL && $this->{'Controllable'} && $this->{'TrackMotion'} && ( $this->{'Function'} == 'Modect' || $this->{'Function'} == 'Mocord' ) ) { daemonControl( 'start', 'zmtrack.pl', '-m '.$this->{'Id'} ); } if ( $mode == 'reload' ) { daemonControl( 'reload', 'zma', '-m '.$this->{'Id'} ); } } } // end if we are on the recording server } // end public function zmaControl public function GroupIds( $new='') { if ( $new != '' ) { if(!is_array($new)) { $this->{'GroupIds'} = array($new); } else { $this->{'GroupIds'} = $new; } } if ( !array_key_exists('GroupIds', $this) ) { if ( array_key_exists('Id', $this) and $this->{'Id'} ) { $this->{'GroupIds'} = dbFetchAll('SELECT GroupId FROM Groups_Monitors WHERE MonitorId=?', 'GroupId', array($this->{'Id'}) ); if ( ! $this->{'GroupIds'} ) $this->{'GroupIds'} = array(); } else { $this->{'GroupIds'} = array(); } } return $this->{'GroupIds'}; } public function delete() { $this->zmaControl('stop'); $this->zmcControl('stop'); // If fast deletes are on, then zmaudit will clean everything else up later // If fast deletes are off and there are lots of events then this step may // well time out before completing, in which case zmaudit will still tidy up if ( !ZM_OPT_FAST_DELETE ) { $markEids = dbFetchAll('SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($this->{'Id'})); foreach($markEids as $markEid) deleteEvent($markEid); deletePath(ZM_DIR_EVENTS.'/'.basename($this->{'Name'})); deletePath(ZM_DIR_EVENTS.'/'.$this->{'Id'}); $Storage = $this->Storage(); if ( $Storage->Path() != ZM_DIR_EVENTS ) { deletePath($Storage->Path().'/'.basename($this->{'Name'})); deletePath($Storage->Path().'/'.$this->{'Id'}); } } // end if ZM_OPT_FAST_DELETE // This is the important stuff dbQuery('DELETE FROM Zones WHERE MonitorId = ?', array($this->{'Id'})); if ( ZM_OPT_X10 ) dbQuery('DELETE FROM TriggersX10 WHERE MonitorId=?', array($this->{'Id'})); dbQuery('DELETE FROM Monitors WHERE Id = ?', array($this->{'Id'})); // Deleting a Monitor does not affect the order, just creates a gap in the sequence. Who cares? // fixSequences(); } // end function delete public function Storage( $new = null ) { if ( $new ) { $this->{'Storage'} = $new; } if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) { $this->{'Storage'} = isset($this->{'StorageId'}) ? Storage::find_one(array('Id'=>$this->{'StorageId'})) : new Storage(NULL); if ( ! $this->{'Storage'} ) $this->{'Storage'} = new Storage(NULL); } return $this->{'Storage'}; } public function Source( ) { $source = ''; if ( $this->{'Type'} == 'Local' ) { $source = $this->{'Device'}.' ('.$this->{'Channel'}.')'; } elseif ( $this->{'Type'} == 'Remote' ) { $source = preg_replace( '/^.*@/', '', $this->{'Host'} ); if ( $this->{'Port'} != '80' and $this->{'Port'} != '554' ) { $source .= ':'.$this->{'Port'}; } } elseif ( $this->{'Type'} == 'File' || $this->{'Type'} == 'cURL' ) { $source = preg_replace( '/^.*\//', '', $this->{'Path'} ); } elseif ( $this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite' ) { $url_parts = parse_url( $this->{'Path'} ); if ( ZM_WEB_FILTER_SOURCE == 'Hostname' ) { # Filter out everything but the hostname if ( isset($url_parts['host']) ) { $source = $url_parts['host']; } else { $source = $this->{'Path'}; } } elseif ( ZM_WEB_FILTER_SOURCE == "NoCredentials" ) { # Filter out sensitive and common items unset($url_parts['user']); unset($url_parts['pass']); #unset($url_parts['scheme']); unset($url_parts['query']); #unset($url_parts['path']); if ( isset($url_parts['port']) and ( $url_parts['port'] == '80' or $url_parts['port'] == '554' ) ) unset($url_parts['port']); $source = unparse_url($url_parts); } else { # Don't filter anything $source = $this->{'Path'}; } } if ( $source == '' ) { $source = 'Monitor ' . $this->{'Id'}; } return $source; } // end function Source public function Url() { return $this->Server()->Url( ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null ); } } // end class Monitor ?> ZoneMinder-1.32.2/web/includes/Server.php0000644000000000000000000000741213365153155016724 0ustar rootroot null, 'Name' => '', 'Hostname' => '', 'zmaudit' => 1, 'zmstats' => 1, 'zmtrigger' => 0, ); public function __construct( $IdOrRow = NULL ) { global $server_cache; $row = NULL; if ( $IdOrRow ) { if ( is_integer($IdOrRow) or ctype_digit($IdOrRow) ) { $row = dbFetchOne('SELECT * FROM Servers WHERE Id=?', NULL, array($IdOrRow)); if ( !$row ) { Error("Unable to load Server record for Id=" . $IdOrRow); } } elseif ( is_array($IdOrRow) ) { $row = $IdOrRow; } } # end if isset($IdOrRow) if ( $row ) { foreach ($row as $k => $v) { $this->{$k} = $v; } $server_cache[$row['Id']] = $this; } else { $this->{'Name'} = ''; $this->{'Hostname'} = ''; } } public function Url( $port = null ) { $url = ZM_BASE_PROTOCOL . '://'; if ( $this->Id() ) { $url .= $this->Hostname(); } else { $url .= $_SERVER['SERVER_NAME']; } if ( $port ) { $url .= ':'.$port; } else { $url .= ':'.$_SERVER['SERVER_PORT']; } $url .= $_SERVER['PHP_SELF']; return $url; } public function Hostname() { if ( isset( $this->{'Hostname'} ) and ( $this->{'Hostname'} != '' ) ) { return $this->{'Hostname'}; } return $this->{'Name'}; } public function __call($fn, array $args){ if ( count($args) ) { $this->{$fn} = $args[0]; } if ( array_key_exists($fn, $this) ) { return $this->{$fn}; } else { if ( array_key_exists( $fn, $this->defaults ) ) { return $this->defaults{$fn}; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning( "Unknown function call Server->$fn from $file:$line" ); } } } public static function find( $parameters = null, $options = null ) { $filters = array(); $sql = 'SELECT * FROM Servers '; $values = array(); if ( $parameters ) { $fields = array(); $sql .= 'WHERE '; foreach ( $parameters as $field => $value ) { if ( $value == null ) { $fields[] = $field.' IS NULL'; } else if ( is_array( $value ) ) { $func = function(){return '?';}; $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; $values += $value; } else { $fields[] = $field.'=?'; $values[] = $value; } } $sql .= implode(' AND ', $fields ); } if ( $options ) { if ( isset($options['order']) ) { $sql .= ' ORDER BY ' . $options['order']; } if ( isset($options['limit']) ) { if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { $sql .= ' LIMIT ' . $options['limit']; } else { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Error("Invalid value for limit(".$options['limit'].") passed to Server::find from $file:$line"); return array(); } } } $results = dbFetchAll( $sql, NULL, $values ); if ( $results ) { return array_map( function($id){ return new Server($id); }, $results ); } return array(); } public static function find_one( $parameters = array() ) { global $server_cache; if ( ( count($parameters) == 1 ) and isset($parameters['Id']) and isset($server_cache[$parameters['Id']]) ) { return $server_cache[$parameters['Id']]; } $results = Server::find( $parameters, array('limit'=>1) ); if ( ! sizeof($results) ) { return; } return $results[0]; } } ?> ZoneMinder-1.32.2/web/includes/functions.php0000644000000000000000000022464013365153155017472 0ustar rootrootHostname(),'/').'/', $_SERVER['HTTP_ORIGIN']) ) { $valid = true; Logger::Debug("Setting Access-Controll-Allow-Origin from " . $_SERVER['HTTP_ORIGIN']); header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); header('Access-Control-Allow-Headers: x-requested-with,x-request'); break; } } if ( !$valid ) { Warning($_SERVER['HTTP_ORIGIN'] . ' is not found in servers list.'); } } } function getStreamSrc( $args, $querySep='&' ) { $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $args[] = 'auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); } elseif ( ZM_AUTH_RELAY == 'plain' ) { $args[] = 'user='.$_SESSION['username']; $args[] = 'pass='.$_SESSION['password']; } elseif ( ZM_AUTH_RELAY == 'none' ) { $args[] = 'user='.$_SESSION['username']; } } if ( !in_array( 'mode=single', $args ) && !empty($GLOBALS['connkey']) ) { $args[] = 'connkey='.$GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { $args[] = 'rand='.time(); } if ( count($args) ) { $streamSrc .= '?'.join( $querySep, $args ); } return( $streamSrc ); } function getMimeType( $file ) { if ( function_exists('mime_content_type') ) { return( mime_content_type( $file ) ); } elseif ( function_exists('finfo_file') ) { $finfo = finfo_open( FILEINFO_MIME ); $mimeType = finfo_file( $finfo, $file ); finfo_close($finfo); return( $mimeType ); } return( trim( exec( 'file -bi '.escapeshellarg( $file ).' 2>/dev/null' ) ) ); } function outputVideoStream( $id, $src, $width, $height, $format, $title='' ) { echo getVideoStreamHTML( $id, $src, $width, $height, $format, $title ); } function getVideoStreamHTML( $id, $src, $width, $height, $format, $title='' ) { $html = ''; $width = validInt($width); $height = validInt($height); $title = validHtmlStr($title); if ( file_exists( $src ) ) { $mimeType = getMimeType( $src ); } else { switch( $format ) { case 'asf' : $mimeType = 'video/x-ms-asf'; break; case 'avi' : case 'wmv' : $mimeType = 'video/x-msvideo'; break; case 'mov' : $mimeType = 'video/quicktime'; break; case 'mpg' : case 'mpeg' : $mimeType = 'video/mpeg'; break; case 'swf' : $mimeType = 'application/x-shockwave-flash'; break; case '3gp' : $mimeType = 'video/3gpp'; break; default : $mimeType = "video/$format"; break; } } if ( !$mimeType || ($mimeType == 'application/octet-stream') ) $mimeType = 'video/'.$format; if ( ZM_WEB_USE_OBJECT_TAGS ) { switch( $mimeType ) { case 'video/x-ms-asf' : case 'video/x-msvideo' : case 'video/mp4' : { if ( isWindows() ) { return ' '; } } case 'video/quicktime' : { return ' '; } case 'application/x-shockwave-flash' : { return ' '; } } # end switch } # end if use object tags return ' '; } function outputImageStream( $id, $src, $width, $height, $title='' ) { echo getImageStreamHTML( $id, $src, $width, $height, $title ); } function getImageStreamHTML( $id, $src, $width, $height, $title='' ) { if ( canStreamIframe() ) { return '