From 357ea4c3a69440b9858b4561f943bf853cde39b0 Mon Sep 17 00:00:00 2001 From: ggets <4815620+ggetsov@users.noreply.github.com> Date: Fri, 11 Feb 2022 10:49:01 +0200 Subject: [PATCH 1/2] php 8 error handler param incompatibility fix --- src/phpagi.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phpagi.php b/src/phpagi.php index 66247ba..6120897 100644 --- a/src/phpagi.php +++ b/src/phpagi.php @@ -1756,7 +1756,7 @@ function make_folder($folder, $perms=0755) * @param integer $line line number of error * @param array $context variables in the current scope */ -function phpagi_error_handler($level, $message, $file, $line, $context) +function phpagi_error_handler($level, $message, $file, $line, $context=null) { if(ini_get('error_reporting') == 0) return; // this happens with an @ From 8fd8ea7c7b5bdf7641876272e5a21499cdef7799 Mon Sep 17 00:00:00 2001 From: ggets <4815620+ggets@users.noreply.github.com> Date: Sat, 12 Feb 2022 09:53:12 +0200 Subject: [PATCH 2/2] re-release a working fork under PHP 8 --- .gitignore | 8 +- COPYING | 916 ++++----- README.md | 130 +- composer.json | 26 +- docs/CHANGELOG | 362 ++-- docs/README.phpagi | 198 +- docs/README.phpagi-asmanager | 444 ++-- docs/README.phpagi-fastagi | 92 +- docs/fastagi.xinetd | 28 +- docs/phpagi.example.conf | 52 +- mkdocs.php | 14 +- src/phpagi-asmanager.php | 1950 +++++++++--------- src/phpagi-fastagi.php | 158 +- src/phpagi.php | 3698 +++++++++++++++++----------------- 14 files changed, 4038 insertions(+), 4038 deletions(-) diff --git a/.gitignore b/.gitignore index 347a927..0fbb3a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/.idea/ -/composer.lock -/composer.phar -/vendor/ +/.idea/ +/composer.lock +/composer.phar +/vendor/ diff --git a/COPYING b/COPYING index 3b20440..f95d0f6 100644 --- a/COPYING +++ b/COPYING @@ -1,458 +1,458 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -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 and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, 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 library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete 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 distribute a copy of this License along with the -Library. - - 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 Library or any portion -of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -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 Library, 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 Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you 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. - - If distribution of 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 satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be 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. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library 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. - - 9. 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 Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -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 with -this License. - - 11. 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 Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library 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 Library. - -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. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library 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. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser 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 Library -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 Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -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 - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "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 -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. 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 LIBRARY 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 -LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md index e028a98..7099513 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,65 @@ -PHPAGI README -------------- - -Welcome to PHPAGI. - -phpagi is a set of PHP classes for use in developing applications with -the Asterisk Gateway Interface, and is licensed under the GNU Lesser -General Public License (see COPYING for terms). - -This release (version 2) of the phpagi classes is a significant overhaul -from the old version 1 library. API functions have been renamed and -restructured. - -Version 1 of phpagi is no longer supported, but will continue to be -available for historical purposes. We strongly encourage you to migrate -to this new version. - -If you have developed software based around phpagi, we'd like to hear from -you! Drop us a note, and indicate whether you'd like us to list your -application on our website. - -## Installation - -The preferred way to install this extension is through [composer](https://getcomposer.org/download/). - -Either run - -```bash -$ composer require welltime/phpagi ^2.20 -``` - -or add - -``` -"welltime/phpagi": "^2.20" -``` - -to the ```require``` section of your `composer.json` file. - -FILES ------ -* phpagi.php - The main phpagi class. -* phpagi-asmanager.php - The Asterisk Manager class. -* phpagi-fastagi.php - FastAGI class. - -* docs/ - README files for the classes. -* api-docs/ - API Documentation (html) - -DOCS ----- -* README.phpagi - The main phpagi README -* README.phpagi-asmanager - The phpagi asterisk manager README -* README.phpagi-fastagi - phpagi fastagi README -* CHANGELOG - Change Log. - -* phpagi.conf - An example configuration file for phpagi. -* fastagi.xinetd - xinetd.conf sample configuration for fastagi - -SUPPORT -------- - -Support for phpagi is available from the project website. - - * https://github.com/welltime/phpagi - +PHPAGI README +------------- + +Welcome to PHPAGI. + +phpagi is a set of PHP classes for use in developing applications with +the Asterisk Gateway Interface, and is licensed under the GNU Lesser +General Public License (see COPYING for terms). + +This release (version 2) of the phpagi classes is a significant overhaul +from the old version 1 library. API functions have been renamed and +restructured. + +Version 1 of phpagi is no longer supported, but will continue to be +available for historical purposes. We strongly encourage you to migrate +to this new version. + +If you have developed software based around phpagi, we'd like to hear from +you! Drop us a note, and indicate whether you'd like us to list your +application on our website. + +## Installation + +The preferred way to install this extension is through [composer](https://getcomposer.org/download/). + +Either run + +```bash +$ composer require ggets/phpagi ^2.20 +``` + +or add + +``` +"ggets/phpagi": "^2.20" +``` + +to the ```require``` section of your `composer.json` file. + +FILES +----- +* phpagi.php - The main phpagi class. +* phpagi-asmanager.php - The Asterisk Manager class. +* phpagi-fastagi.php - FastAGI class. + +* docs/ - README files for the classes. +* api-docs/ - API Documentation (html) + +DOCS +---- +* README.phpagi - The main phpagi README +* README.phpagi-asmanager - The phpagi asterisk manager README +* README.phpagi-fastagi - phpagi fastagi README +* CHANGELOG - Change Log. + +* phpagi.conf - An example configuration file for phpagi. +* fastagi.xinetd - xinetd.conf sample configuration for fastagi + +SUPPORT +------- + +Support for phpagi is available from the project website. + + * https://github.com/ggets/phpagi + diff --git a/composer.json b/composer.json index 1c84ec6..cc5bf29 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,13 @@ -{ - "name": "welltime/phpagi", - "description": "PHPAGI is a PHP class for the Asterisk Gateway Interface.", - "type": "library", - "license": "LGPL-2.1-or-later", - "autoload": { - "classmap": [ - "src/phpagi.php", - "src/phpagi-asmanager.php" - ] - }, - "require": {} -} +{ + "name": "ggets/phpagi", + "description": "PHPAGI is a PHP class for the Asterisk Gateway Interface.", + "type": "library", + "license": "LGPL-2.1-or-later", + "autoload": { + "classmap": [ + "src/phpagi.php", + "src/phpagi-asmanager.php" + ] + }, + "require": {} +} diff --git a/docs/CHANGELOG b/docs/CHANGELOG index c279fe8..b09625d 100644 --- a/docs/CHANGELOG +++ b/docs/CHANGELOG @@ -1,181 +1,181 @@ ----------- -CHANGES ----------- -Sep 18, 2010 - Matthew Asham - * Add option_delim to AGI to specify application option delimiter - * Changed constructor method to __construct - -Sep 17, 2010 - Matthew Asham - * BACKWARDS COMPATABILITY WARNING: renamed goto() method to setContext() to address PHP 5.3 reserved keyword. tracker #3031708 - * add get_fullvariable to AGI class - * add boolean flag to get_variable and get_fullvariable methods to - return only the value - tracker #1736629 - * initialize $ret variable in say_punctuation - * add string parameter to noop - * fix escape issue in database_get - * make_folder now returns true or false depending on success - * update exec_setlanguage to use asterisk 1.6+ language - -May 19, 2010 - Roland Hu - * new methods: set_var(), set_global_var() - * dialplan bug (using pipe instead of comma) is fixed in exec_dial() and exec_goto() - * code formatting - -June 8, 2010 - Matthew Asham - * move to svn from cvs - -May 25, 2005 - David Eder - * Added fastpass support. - -May 18, 2005 - David Eder - * Added phpagi_1.php as a wrapper class for 1.12 compatibility. - -March 25, 2005 - David Eder - * Changed the way text2wave and swift are executed for better compatibility. - * Added caching to text2wav and swift. - -March 12, 2005 - David Eder - * fixed autohangup in phpagi-asmanager.php, renamed to set_autohangup. - * Added more documentation to phpagi-asmanager.php. - * Added weather example. - -March 4, 2005 - David Eder - * FastAGI via xinetd - -February 17, 2005 - David Eder - * Fixed bugs with error handler. - -February 16, 2005 - David Eder - * Added Cepstral swift TTS patch from C. Arbusti of Soluzioni Vocali ( http://soluzionivocali.it ). - * Extended swift TTS functionality. - * Moved tempdir from [festival] to [phpagi] so that it can be shared with other extensions. - -February 2, 2005 - David Eder - * More documentation. - * Reworked and reintegrated AGI_AsteriskManager class. - -January 21, 2005 - David Eder - * Added exec_dial($type, $identifier, $timeout=NULL, $options=NULL, $url=NULL). - * Added exec_goto($a, $b=NULL, $c=NULL). - * Fixed bugs in evaluate, better support of multiline and closed input and output. - * Fixed bugs in config initialization. - -January 19, 2005 - David Eder - * SUMMARY: - * Massive restructuring! - * Updated to use more PHP internal functions. - * Updated function arguments to represent their AGI function's arguments. - * Functions now return a consistent result array. - * Added phpdoc documentation. - * Removed functions that can be done with internal PHP or AGI functions. - * Enhanced error handler. - * - * Goals of changes: - * - * It is important for a language API to not wander too far from the general - * API. Functions that are named differently have been updated. Function - * arguments that do not match the AGI API have been updated. Underscores - * have been substituted for spaces to make them compatible with PHP function - * names. Optional arguments in AGI should remain optional if possible. Return - * values should be consistent. Functions need to be better documented. - * - * Result: - * - * The return from most functions is now - * array('code'=>$code, 'result'=>$result, 'data'=>$data) - * ['data'] still needs some work. - * - * removed class variables: - * $response - It was no longer used with the new return structure. - * - * removed functions: - * agi_is_error($retarr) - It was no longer used with the new return - * structure. Each function has it's own result that must be evalutated - * by the programmer, as the return values are often specific to the function. - * agi_readresult($str=FALSE) - It was no longer used with the new return structure. - * agi_response_code() - It was no longer used with the new return structure. - * agi_response_result() - It was no longer used with the new return structure. - * agi_response_data() - It was no longer used with the new return structure. - * agi_response_var($var) - It was no longer used with the new return structure. - * agi_response_is_error() - see agi_is_error - * agi_read() - It was deprecated - * con_print_r($arr,$label='',$lvl=0) - Use print_r($arr, true) in conjunction with conlog - * agi_getdtmf($len,$timeout,$terminator=FALSE,$prompt=FALSE) - use get_data - * agi_dtmf2text($len,$timeout,$terminator=FALSE,$prompt=FALSE) - use text_input - * arr2str($arr) - use PHP function join - * config_load($file) - use PHP function parse_ini_file - * enum_lookup($telnumber,$rDNS="e164.org") - use exec_enumlookup - * enum_txtlookup($telnumber,$rDNS="e164.org") - use exec_enumlookup - * - * added functions: - * answer() - * autohangup($time=0) - * exec($application, $options) - * get_data($filename, $timeout=NULL, $max_digits=NULL) - * receive_char($timeout=-1) - * say_phonetic($text, $escape_digits='') - * set_context($context) - * set_extension($extension) - * set_priority($priority) - * tdd_mode($setting) - * wait_for_digit($timeout=-1) - * database_deltree($family, $keytree='') - * noop() - * set_music($enabled=true, $class='') - * exec_absolutetimeout($seconds=0) - * exec_agi($command, $args) - * exec_enumlookup($exten) - * text_input($mode='NUMERIC') - * say_punctuation($text, $escape_digits='', $frequency=8000) - * which($cmd, $checkpath=NULL) - * make_folder($folder, $perms=0755) - * updated functions: - * agi_exec($str) -> evaluate($command) - exec is an AGI function - * agi_verbose($str,$vbl=1) -> verbose($message, $level=1) - consistency with AGI - * db_get($family,$key) -> database_get($family, $key) - consistency with AGI - * db_put($family,$key,$val) -> database_put($family, $key, $value) - consistency with AGI - * db_del($family,$key) -> database_del($family, $key) - consistency with AGI - * get_var($var) -> get_variable($variable) - consistency with AGI - * set_var($var,$val) -> set_variable($variable, $value) - consistency with AGI - * agi_hangup() -> hangup($channel='') - consistency with AGI - * agi_channel_status($channel) -> channel_status($channel='') - consistency with AGI - * agi_recordfile($file,$format,$timeout=5000,$prompt=FALSE) -> record_file($file, $format, $escape_digits='', $timeout=-1, $beep=false, $silence=NULL) - consistency with AGI - * agi_play($file) -> stream_file($filename, $escape_digits='', $offset=0) - consistency with AGI - * agi_goto($con,$ext='s',$pri=1) -> goto($context, $extension='s', $priority=1) - consistency with AGI - * agi_saydigits($digits) -> say_digits($digits, $escape_digits='') - consistency with AGI - * agi_saynumber($number) -> say_number($number, $escape_digits='') - consistency with AGI - * agi_saytime($time="") -> say_time($time=NULL, $escape_digits='') - consistency with AGI - * agi_setlanguage($language="en") -> exec_setlanguage($language='en') - consistency with AGI - * text2wav($text) -> text2wav($text, $escape_digits='', $frequency=8000) - consistency with AGI - * phpagi_error_handler($errno, $errstr, $errfile, $errline) -> phpagi_error_handler($level, $message, $file, $line, $context) - I needed better error handling - -August 29, 2004 - - * Fixed db_get now returns a value - * Fixed db_put now stores a value - * Added enum_lookup, requires external "dig" utility. - * Tweak con_print_r now dumps arrays, displays variable type. - * Added enum_txtlookup, requires external "dig" utility". - * Added parse_callerid, thanks to http://www.sbuehl.com/projects/asterisk/asterisk-howto-3.html - -August 26, 2004 - - * Added agi_verbose - * Added debug config-option to shut conlog up - * Added agi_saydigits, agi_saynumber, agi_saytime, agi_setlanguage - -April 8, 2004 - v1.5 internal - * Added agi_goto - David Croft - * Added this changelog (maintain it eh?) - Matthew Asham - * Added quote fixing to conlog - David Croft - * Added new parameter to AGI constructor, $configopt array - Matthew Asham - - Added phpagi_error_handler - David Croft - - - -March 20, 2004 - v1.5 - * Fixed buffering bugs - David Croft - * added demo app. - Matthew Asham - * misc stuff. - Matthew Asham - -November 2003 - * Initial version - +---------- +CHANGES +---------- +Sep 18, 2010 - Matthew Asham + * Add option_delim to AGI to specify application option delimiter + * Changed constructor method to __construct + +Sep 17, 2010 - Matthew Asham + * BACKWARDS COMPATABILITY WARNING: renamed goto() method to setContext() to address PHP 5.3 reserved keyword. tracker #3031708 + * add get_fullvariable to AGI class + * add boolean flag to get_variable and get_fullvariable methods to + return only the value - tracker #1736629 + * initialize $ret variable in say_punctuation + * add string parameter to noop + * fix escape issue in database_get + * make_folder now returns true or false depending on success + * update exec_setlanguage to use asterisk 1.6+ language + +May 19, 2010 - Roland Hu + * new methods: set_var(), set_global_var() + * dialplan bug (using pipe instead of comma) is fixed in exec_dial() and exec_goto() + * code formatting + +June 8, 2010 - Matthew Asham + * move to svn from cvs + +May 25, 2005 - David Eder + * Added fastpass support. + +May 18, 2005 - David Eder + * Added phpagi_1.php as a wrapper class for 1.12 compatibility. + +March 25, 2005 - David Eder + * Changed the way text2wave and swift are executed for better compatibility. + * Added caching to text2wav and swift. + +March 12, 2005 - David Eder + * fixed autohangup in phpagi-asmanager.php, renamed to set_autohangup. + * Added more documentation to phpagi-asmanager.php. + * Added weather example. + +March 4, 2005 - David Eder + * FastAGI via xinetd + +February 17, 2005 - David Eder + * Fixed bugs with error handler. + +February 16, 2005 - David Eder + * Added Cepstral swift TTS patch from C. Arbusti of Soluzioni Vocali ( http://soluzionivocali.it ). + * Extended swift TTS functionality. + * Moved tempdir from [festival] to [phpagi] so that it can be shared with other extensions. + +February 2, 2005 - David Eder + * More documentation. + * Reworked and reintegrated AGI_AsteriskManager class. + +January 21, 2005 - David Eder + * Added exec_dial($type, $identifier, $timeout=NULL, $options=NULL, $url=NULL). + * Added exec_goto($a, $b=NULL, $c=NULL). + * Fixed bugs in evaluate, better support of multiline and closed input and output. + * Fixed bugs in config initialization. + +January 19, 2005 - David Eder + * SUMMARY: + * Massive restructuring! + * Updated to use more PHP internal functions. + * Updated function arguments to represent their AGI function's arguments. + * Functions now return a consistent result array. + * Added phpdoc documentation. + * Removed functions that can be done with internal PHP or AGI functions. + * Enhanced error handler. + * + * Goals of changes: + * + * It is important for a language API to not wander too far from the general + * API. Functions that are named differently have been updated. Function + * arguments that do not match the AGI API have been updated. Underscores + * have been substituted for spaces to make them compatible with PHP function + * names. Optional arguments in AGI should remain optional if possible. Return + * values should be consistent. Functions need to be better documented. + * + * Result: + * + * The return from most functions is now + * array('code'=>$code, 'result'=>$result, 'data'=>$data) + * ['data'] still needs some work. + * + * removed class variables: + * $response - It was no longer used with the new return structure. + * + * removed functions: + * agi_is_error($retarr) - It was no longer used with the new return + * structure. Each function has it's own result that must be evalutated + * by the programmer, as the return values are often specific to the function. + * agi_readresult($str=FALSE) - It was no longer used with the new return structure. + * agi_response_code() - It was no longer used with the new return structure. + * agi_response_result() - It was no longer used with the new return structure. + * agi_response_data() - It was no longer used with the new return structure. + * agi_response_var($var) - It was no longer used with the new return structure. + * agi_response_is_error() - see agi_is_error + * agi_read() - It was deprecated + * con_print_r($arr,$label='',$lvl=0) - Use print_r($arr, true) in conjunction with conlog + * agi_getdtmf($len,$timeout,$terminator=FALSE,$prompt=FALSE) - use get_data + * agi_dtmf2text($len,$timeout,$terminator=FALSE,$prompt=FALSE) - use text_input + * arr2str($arr) - use PHP function join + * config_load($file) - use PHP function parse_ini_file + * enum_lookup($telnumber,$rDNS="e164.org") - use exec_enumlookup + * enum_txtlookup($telnumber,$rDNS="e164.org") - use exec_enumlookup + * + * added functions: + * answer() + * autohangup($time=0) + * exec($application, $options) + * get_data($filename, $timeout=NULL, $max_digits=NULL) + * receive_char($timeout=-1) + * say_phonetic($text, $escape_digits='') + * set_context($context) + * set_extension($extension) + * set_priority($priority) + * tdd_mode($setting) + * wait_for_digit($timeout=-1) + * database_deltree($family, $keytree='') + * noop() + * set_music($enabled=true, $class='') + * exec_absolutetimeout($seconds=0) + * exec_agi($command, $args) + * exec_enumlookup($exten) + * text_input($mode='NUMERIC') + * say_punctuation($text, $escape_digits='', $frequency=8000) + * which($cmd, $checkpath=NULL) + * make_folder($folder, $perms=0755) + * updated functions: + * agi_exec($str) -> evaluate($command) - exec is an AGI function + * agi_verbose($str,$vbl=1) -> verbose($message, $level=1) - consistency with AGI + * db_get($family,$key) -> database_get($family, $key) - consistency with AGI + * db_put($family,$key,$val) -> database_put($family, $key, $value) - consistency with AGI + * db_del($family,$key) -> database_del($family, $key) - consistency with AGI + * get_var($var) -> get_variable($variable) - consistency with AGI + * set_var($var,$val) -> set_variable($variable, $value) - consistency with AGI + * agi_hangup() -> hangup($channel='') - consistency with AGI + * agi_channel_status($channel) -> channel_status($channel='') - consistency with AGI + * agi_recordfile($file,$format,$timeout=5000,$prompt=FALSE) -> record_file($file, $format, $escape_digits='', $timeout=-1, $beep=false, $silence=NULL) - consistency with AGI + * agi_play($file) -> stream_file($filename, $escape_digits='', $offset=0) - consistency with AGI + * agi_goto($con,$ext='s',$pri=1) -> goto($context, $extension='s', $priority=1) - consistency with AGI + * agi_saydigits($digits) -> say_digits($digits, $escape_digits='') - consistency with AGI + * agi_saynumber($number) -> say_number($number, $escape_digits='') - consistency with AGI + * agi_saytime($time="") -> say_time($time=NULL, $escape_digits='') - consistency with AGI + * agi_setlanguage($language="en") -> exec_setlanguage($language='en') - consistency with AGI + * text2wav($text) -> text2wav($text, $escape_digits='', $frequency=8000) - consistency with AGI + * phpagi_error_handler($errno, $errstr, $errfile, $errline) -> phpagi_error_handler($level, $message, $file, $line, $context) - I needed better error handling + +August 29, 2004 - + * Fixed db_get now returns a value + * Fixed db_put now stores a value + * Added enum_lookup, requires external "dig" utility. + * Tweak con_print_r now dumps arrays, displays variable type. + * Added enum_txtlookup, requires external "dig" utility". + * Added parse_callerid, thanks to http://www.sbuehl.com/projects/asterisk/asterisk-howto-3.html + +August 26, 2004 - + * Added agi_verbose + * Added debug config-option to shut conlog up + * Added agi_saydigits, agi_saynumber, agi_saytime, agi_setlanguage + +April 8, 2004 - v1.5 internal + * Added agi_goto - David Croft + * Added this changelog (maintain it eh?) - Matthew Asham + * Added quote fixing to conlog - David Croft + * Added new parameter to AGI constructor, $configopt array - Matthew Asham + - Added phpagi_error_handler - David Croft + + + +March 20, 2004 - v1.5 + * Fixed buffering bugs - David Croft + * added demo app. - Matthew Asham + * misc stuff. - Matthew Asham + +November 2003 + * Initial version + diff --git a/docs/README.phpagi b/docs/README.phpagi index 8576332..4e49e66 100644 --- a/docs/README.phpagi +++ b/docs/README.phpagi @@ -1,99 +1,99 @@ -phpagi: an AGI class written in PHP - - Matthew Asham - https://github.com/welltime/phpagi - -Original version: - http://phpagi.sourceforge.net/ - -Contributions by: - Florian Overkamp - David Eder - Roland Hu - Others - -OVERVIEW --------- - -Include it: - -require 'phpagi.php'; - -Construct it: - -$agi = new AGI(); - -This creates a new AGI object with all the agi vars read in, you're now ready -to rock and roll. - -Answer the line. - -$agi->answer(); - -Play a file. - -$agi->stream_file('somefile.gsm'); - -Record a file. - -$agi->record_file($file, $format, $escape_digits='', $timeout=-1, $beep=false, $silence=NULL) - -There's more, read the class or refer to the PHPAGI website for more information. - - This README is seriousley lacking. - -CONFIGURATION -------------- - -phpagi supports an ini style configuration file, and run time configuration. - -By default the class reads in the contents of /etc/asterisk/phpagi.conf into -$this->config. The format of the ini file is as follows: - -[examplesection] -foo=bar -bar=dew -dew=pale ale - -This is read into $this->config as: - -$this->config['examplesection']['foo']='bar'; -$this->config['examplesection']['bar]='dew'; -$this->config['examplesection']['dew']='pale ale'; - - -Run time configuration is also supported. Pass an array of variables and values as the -second parameter to the AGI constructor, these fields are stored in $this->config['phpagi']. - -ie: - -$myconfig=array( - "error_handler"="true" -); - - $agi=new AGI("/etc/asterisk/phpagi.conf",$myconfig); - - -The following run-time configuration options are used by the phpagi class to change behaviour. They -are all contained in the $this->config['phpagi'] array. - - * error_handler - set to "true" (string) to enable php debugging - - -EXAMPLE APPLICATION -------------------- - -ping.php is an example phpagi application. it asks for an IP address to be -entered, and reads back the results of a "ping" summary. - -to use it, you'll need festival installed. see http://www.voip-info.org/tiki-index.php?page=Asterisk+festival+installation -for all the groovy bits to get festival working with asterisk, then configure -phpagi to use it too: - -in /etc/asterisk/phpagi.conf: - -[festival] -text2wave=/usr/src/festival/bin/text2wave -tempdir=/var/lib/asterisk/sounds/tmp/ - - +phpagi: an AGI class written in PHP + + Matthew Asham + https://github.com/ggets/phpagi + +Original version: + http://phpagi.sourceforge.net/ + +Contributions by: + Florian Overkamp + David Eder + Roland Hu + Others + +OVERVIEW +-------- + +Include it: + +require 'phpagi.php'; + +Construct it: + +$agi = new AGI(); + +This creates a new AGI object with all the agi vars read in, you're now ready +to rock and roll. + +Answer the line. + +$agi->answer(); + +Play a file. + +$agi->stream_file('somefile.gsm'); + +Record a file. + +$agi->record_file($file, $format, $escape_digits='', $timeout=-1, $beep=false, $silence=NULL) + +There's more, read the class or refer to the PHPAGI website for more information. + + This README is seriousley lacking. + +CONFIGURATION +------------- + +phpagi supports an ini style configuration file, and run time configuration. + +By default the class reads in the contents of /etc/asterisk/phpagi.conf into +$this->config. The format of the ini file is as follows: + +[examplesection] +foo=bar +bar=dew +dew=pale ale + +This is read into $this->config as: + +$this->config['examplesection']['foo']='bar'; +$this->config['examplesection']['bar]='dew'; +$this->config['examplesection']['dew']='pale ale'; + + +Run time configuration is also supported. Pass an array of variables and values as the +second parameter to the AGI constructor, these fields are stored in $this->config['phpagi']. + +ie: + +$myconfig=array( + "error_handler"="true" +); + + $agi=new AGI("/etc/asterisk/phpagi.conf",$myconfig); + + +The following run-time configuration options are used by the phpagi class to change behaviour. They +are all contained in the $this->config['phpagi'] array. + + * error_handler - set to "true" (string) to enable php debugging + + +EXAMPLE APPLICATION +------------------- + +ping.php is an example phpagi application. it asks for an IP address to be +entered, and reads back the results of a "ping" summary. + +to use it, you'll need festival installed. see http://www.voip-info.org/tiki-index.php?page=Asterisk+festival+installation +for all the groovy bits to get festival working with asterisk, then configure +phpagi to use it too: + +in /etc/asterisk/phpagi.conf: + +[festival] +text2wave=/usr/src/festival/bin/text2wave +tempdir=/var/lib/asterisk/sounds/tmp/ + + diff --git a/docs/README.phpagi-asmanager b/docs/README.phpagi-asmanager index a784e50..20627c9 100644 --- a/docs/README.phpagi-asmanager +++ b/docs/README.phpagi-asmanager @@ -1,222 +1,222 @@ -phpagi-asmanager: an Asterisk Manager class written in PHP - - Matthew Asham - https://github.com/welltime/phpagi - - Original version: - http://phpagi.sourceforge.net/ - ------------------------------------------------------------------------------- - ------------------------------------------------------------------------------- -SECURITY ------------------------------------------------------------------------------- - -Validation: - -******If asterisk is running as root, the manager interface may allow the -execution of arbitrary shell commands as root. If the user can update any -configuration file that can execute arbitrary command (like the dialplan), -the system may be compromised. - -Also, look out for command injection. Consider the following example: - - $as->Events($_POST['events_status']); - -We expect either 'on' or 'off', but the attacker uses: - - "\r\n\r\nAction: Command\r\nCommand: database put forward 54321 19005551212"; - - -Validation is a *must* for all user data. - - -Username and Secret: - -Storing the username and secret in the config file will isolate them from your -code. - -Isolation of username and secret in the config file does not mean that the -script cannot simple read the config file. The config file must be readable -by the script. - - -CREATING A NEW INSTANCE OF THE CLASS ------------------------------------------------------------------------------- - -The class can be created standalone of phpagi.php, or through phpagi. - -STANDALONE: - -require "phpagi-asmanager.php"; - -$as = new AGI_AsteriskManager(); - -FROM PHPAGI: - -require "phpagi.php"; - -$agi = new AGI(); -$as = $agi->new_AsteriskManager(); - -Notes: - -* If the class is created using $agi->new_AsteriskManager(), - AGI_AsteriskManager will use the parent phpagi for logging to the Asterisk - console. - -* phpagi.php will include phpagi-asmanager.php by itself. - * If phpagi-asmanager.php is included _before_ phpagi.php, phpagi.php will - not attempt to re-include it. - * If phpagi.php tries to include phpagi-asmanager.php but is unable to do - so, an error will be echoed to the asterisk console and the script will - continue running normally. in this case the return value of - new_AsteriskManager() will be FALSE. - ------------------------------------------------------------------------------- -CONFIGURATION ------------------------------------------------------------------------------- - -phpagi-asmanager uses the same configuration file as phpagi.conf (usually -/etc/asterisk/phpagi.conf). All configuration information specific to -phpagi-asmanager is contained in the [asmanager] section of the .conf file. - -supported directives: - -[asmanager] -# server to connect to -server=localhost - -# default manager port -port=5038 - -#username for login -username=me_and_only_me - -#password for login -secret=i_am_not_telling - - - ------------------------------------------------------------------------------- -CONNECTING ------------------------------------------------------------------------------- - - $res = $as->connect("localhost", "username", "password"); - if($res == FALSE) { - echo "Connection failed.\n"; - } - elseif($res == TRUE){ - echo "Connection established.\n"; - } - -A port can also be specified for the hostname. eg: - - $res = $as->connect("my.asterisk.server:1234", "username", "port"); - -If the no parameters are specified, the defaults from the config will be used. - - ------------------------------------------------------------------------------- -DISCONNECTING ------------------------------------------------------------------------------- - - $as->disconnect(); - ------------------------------------------------------------------------------- -SENDING REQUESTS ------------------------------------------------------------------------------- - - $as->send_request($eventname, $arrayofparameterstopass); - -send_request() calls wait_request and returns an array of returned data from -the manager. If something went wrong, it returns false. - -wait_request() shouldn't need to be called from a script directly unless you -are implementing merely an event listener. - -wait_request() will also detect events and dispatch any registered event -handlers for the event. - -examples: - - $res = $as->send_request('EventName', - array('Channel'=>'Zap/1/16045551212', - 'SomeParameter'=>'data')); - echo "Dump of returned data:\n"; - foreach($res as $var=>$val) - echo "$var = $val\n"; - - -$res['Response'] will generally be 'Success' on success and 'Error' on -failure. But this is not always true. If $res['Response'] == 'Follows', a -multi-line response will be stored in $res['data']. - -Several manager commands have been aliased for convenience. See below. - ------------------------------------------------------------------------------- -EVENTS ------------------------------------------------------------------------------- - -TODO: non-blocking socket i/o. - -The class uses event callbacks to process events received from the manager. - -The event callback prototype looks like: - - function dump_event($ecode, $data, $server, $port) - { - echo "received event '$ecode' from $server:$port\n"; - print_r($data); - } - -To register an event call back: - - $as->add_event_handler('eventname', 'eventfunction'); - -eg: - - $as->add_event_handler('registry', 'dump_event'); - - -The special eventname "*" can also be registered. any eventname not -specifically registered will be handled by the "*" handler. If no "*" handler -is defined, the event will be silently ignored. - - ------------------------------------------------------------------------------- -PRECANNED FUNCTIONS ------------------------------------------------------------------------------- - -The following Manager functions have been aliased for convenience: - -AbsoluteTimeout -ChangeMonitor -Command -Events -ExtensionState -GetVar -Hangup -IAXPeers -ListCommands -Logoff -MailboxCount -MailboxStatus -Monitor -Originate -ParkedCalls -Ping -Queues -QueueStatus -Redirect -SetCDRUserField -SetVar -SIPpeers -Status -StopMontor -ZapDialOffhook -ZapDNDoff -ZapDNDon -ZapHangup -ZapTransfer - +phpagi-asmanager: an Asterisk Manager class written in PHP + + Matthew Asham + https://github.com/ggets/phpagi + + Original version: + http://phpagi.sourceforge.net/ + +------------------------------------------------------------------------------ + +------------------------------------------------------------------------------ +SECURITY +------------------------------------------------------------------------------ + +Validation: + +******If asterisk is running as root, the manager interface may allow the +execution of arbitrary shell commands as root. If the user can update any +configuration file that can execute arbitrary command (like the dialplan), +the system may be compromised. + +Also, look out for command injection. Consider the following example: + + $as->Events($_POST['events_status']); + +We expect either 'on' or 'off', but the attacker uses: + + "\r\n\r\nAction: Command\r\nCommand: database put forward 54321 19005551212"; + + +Validation is a *must* for all user data. + + +Username and Secret: + +Storing the username and secret in the config file will isolate them from your +code. + +Isolation of username and secret in the config file does not mean that the +script cannot simple read the config file. The config file must be readable +by the script. + + +CREATING A NEW INSTANCE OF THE CLASS +------------------------------------------------------------------------------ + +The class can be created standalone of phpagi.php, or through phpagi. + +STANDALONE: + +require "phpagi-asmanager.php"; + +$as = new AGI_AsteriskManager(); + +FROM PHPAGI: + +require "phpagi.php"; + +$agi = new AGI(); +$as = $agi->new_AsteriskManager(); + +Notes: + +* If the class is created using $agi->new_AsteriskManager(), + AGI_AsteriskManager will use the parent phpagi for logging to the Asterisk + console. + +* phpagi.php will include phpagi-asmanager.php by itself. + * If phpagi-asmanager.php is included _before_ phpagi.php, phpagi.php will + not attempt to re-include it. + * If phpagi.php tries to include phpagi-asmanager.php but is unable to do + so, an error will be echoed to the asterisk console and the script will + continue running normally. in this case the return value of + new_AsteriskManager() will be FALSE. + +------------------------------------------------------------------------------ +CONFIGURATION +------------------------------------------------------------------------------ + +phpagi-asmanager uses the same configuration file as phpagi.conf (usually +/etc/asterisk/phpagi.conf). All configuration information specific to +phpagi-asmanager is contained in the [asmanager] section of the .conf file. + +supported directives: + +[asmanager] +# server to connect to +server=localhost + +# default manager port +port=5038 + +#username for login +username=me_and_only_me + +#password for login +secret=i_am_not_telling + + + +------------------------------------------------------------------------------ +CONNECTING +------------------------------------------------------------------------------ + + $res = $as->connect("localhost", "username", "password"); + if($res == FALSE) { + echo "Connection failed.\n"; + } + elseif($res == TRUE){ + echo "Connection established.\n"; + } + +A port can also be specified for the hostname. eg: + + $res = $as->connect("my.asterisk.server:1234", "username", "port"); + +If the no parameters are specified, the defaults from the config will be used. + + +------------------------------------------------------------------------------ +DISCONNECTING +------------------------------------------------------------------------------ + + $as->disconnect(); + +------------------------------------------------------------------------------ +SENDING REQUESTS +------------------------------------------------------------------------------ + + $as->send_request($eventname, $arrayofparameterstopass); + +send_request() calls wait_request and returns an array of returned data from +the manager. If something went wrong, it returns false. + +wait_request() shouldn't need to be called from a script directly unless you +are implementing merely an event listener. + +wait_request() will also detect events and dispatch any registered event +handlers for the event. + +examples: + + $res = $as->send_request('EventName', + array('Channel'=>'Zap/1/16045551212', + 'SomeParameter'=>'data')); + echo "Dump of returned data:\n"; + foreach($res as $var=>$val) + echo "$var = $val\n"; + + +$res['Response'] will generally be 'Success' on success and 'Error' on +failure. But this is not always true. If $res['Response'] == 'Follows', a +multi-line response will be stored in $res['data']. + +Several manager commands have been aliased for convenience. See below. + +------------------------------------------------------------------------------ +EVENTS +------------------------------------------------------------------------------ + +TODO: non-blocking socket i/o. + +The class uses event callbacks to process events received from the manager. + +The event callback prototype looks like: + + function dump_event($ecode, $data, $server, $port) + { + echo "received event '$ecode' from $server:$port\n"; + print_r($data); + } + +To register an event call back: + + $as->add_event_handler('eventname', 'eventfunction'); + +eg: + + $as->add_event_handler('registry', 'dump_event'); + + +The special eventname "*" can also be registered. any eventname not +specifically registered will be handled by the "*" handler. If no "*" handler +is defined, the event will be silently ignored. + + +------------------------------------------------------------------------------ +PRECANNED FUNCTIONS +------------------------------------------------------------------------------ + +The following Manager functions have been aliased for convenience: + +AbsoluteTimeout +ChangeMonitor +Command +Events +ExtensionState +GetVar +Hangup +IAXPeers +ListCommands +Logoff +MailboxCount +MailboxStatus +Monitor +Originate +ParkedCalls +Ping +Queues +QueueStatus +Redirect +SetCDRUserField +SetVar +SIPpeers +Status +StopMontor +ZapDialOffhook +ZapDNDoff +ZapDNDon +ZapHangup +ZapTransfer + diff --git a/docs/README.phpagi-fastagi b/docs/README.phpagi-fastagi index 176f4fc..3e982d0 100644 --- a/docs/README.phpagi-fastagi +++ b/docs/README.phpagi-fastagi @@ -1,46 +1,46 @@ -First, in /etc/services, at this line: - -fastagi 4573/tcp # Asterisk AGI - - - -Second, create /etc/xnetd.d/fastagi with: - -# default: off -# description: fastagi is a remote AGI interface -service fastagi -{ - socket_type = stream - user = root - group = nobody - server = - wait = no - protocol = tcp - bind = 127.0.0.1 - disable = no -} - -Make sure you set the path to the phpagi-fastagi.php script. Set the user -and group to a non-root user if none of your scripts need root access. You -might consider using posix_setuid and friends to reduce privileges. Change -the bind address to your outbound IP address or to 0.0.0.0 to allow anyone -to connect. Be sure to read up about xinetd and take advantage of security -features it provides. Fast AGI doesn't provide authentification. It's up -to you to keep unwanted visitors from extracting information from your AGI -implementation. - - -Third, write your code. - -Take special notice of how fastagi.php works: - - 1. $fastagi is initialized as a new AGI. - 2. The script determines which script was requested. - 3. The script is called using - reqire_once($fastagi->request['agi_request']). - 4. Your script takes over. You must not create a new AGI, but - insead use the $fastagi instance that has already been created. - -In your dialplan: - -exten => 5551212, 1, Agi(agi://127.0.0.1/myscript.php) +First, in /etc/services, at this line: + +fastagi 4573/tcp # Asterisk AGI + + + +Second, create /etc/xnetd.d/fastagi with: + +# default: off +# description: fastagi is a remote AGI interface +service fastagi +{ + socket_type = stream + user = root + group = nobody + server = + wait = no + protocol = tcp + bind = 127.0.0.1 + disable = no +} + +Make sure you set the path to the phpagi-fastagi.php script. Set the user +and group to a non-root user if none of your scripts need root access. You +might consider using posix_setuid and friends to reduce privileges. Change +the bind address to your outbound IP address or to 0.0.0.0 to allow anyone +to connect. Be sure to read up about xinetd and take advantage of security +features it provides. Fast AGI doesn't provide authentification. It's up +to you to keep unwanted visitors from extracting information from your AGI +implementation. + + +Third, write your code. + +Take special notice of how fastagi.php works: + + 1. $fastagi is initialized as a new AGI. + 2. The script determines which script was requested. + 3. The script is called using + reqire_once($fastagi->request['agi_request']). + 4. Your script takes over. You must not create a new AGI, but + insead use the $fastagi instance that has already been created. + +In your dialplan: + +exten => 5551212, 1, Agi(agi://127.0.0.1/myscript.php) diff --git a/docs/fastagi.xinetd b/docs/fastagi.xinetd index 3ebf37b..6c848e1 100644 --- a/docs/fastagi.xinetd +++ b/docs/fastagi.xinetd @@ -1,14 +1,14 @@ -# default: off -# description: fastagi is a remote AGI interface -service fastagi -{ - socket_type = stream - user = root - group = nobody - server = /var/lib/asterisk/agi-bin/phpagi/phpagi-fastagi.php - wait = no - protocol = tcp - bind = 127.0.0.1 - disable = no -} - +# default: off +# description: fastagi is a remote AGI interface +service fastagi +{ + socket_type = stream + user = root + group = nobody + server = /var/lib/asterisk/agi-bin/phpagi/phpagi-fastagi.php + wait = no + protocol = tcp + bind = 127.0.0.1 + disable = no +} + diff --git a/docs/phpagi.example.conf b/docs/phpagi.example.conf index ee34cb7..690a77c 100644 --- a/docs/phpagi.example.conf +++ b/docs/phpagi.example.conf @@ -1,26 +1,26 @@ -; example phpagi.conf - -[phpagi] -debug=true ; enable debuging -error_handler=true ; use internal error handler -admin=errors@mydomain.com ; mail errors to -hostname=sip.mydomain.com ; host name of this server -tempdir=/var/spool/asterisk/tmp/ ; temporary directory for storing temporary output - -[asmanager] -server=localhost ; server to connect to -port=5038 ; default manager port -username=me_and_only_me ; username for login -secret=i_am_not_telling ; password for login - -[fastagi] -setuid=true ; drop privileges to owner of script -basedir=/var/lib/asterisk/agi-bin/ ; path to script folder - -[festival] ; text to speech engine -text2wave=/usr/bin/text2wave ; path to text2wave binary - -[cepstral] ; alternate text to speech engine -swift=/opt/swift/bin/swift ; path to switft binary -voice=David ; default voice - +; example phpagi.conf + +[phpagi] +debug=true ; enable debuging +error_handler=true ; use internal error handler +admin=errors@mydomain.com ; mail errors to +hostname=sip.mydomain.com ; host name of this server +tempdir=/var/spool/asterisk/tmp/ ; temporary directory for storing temporary output + +[asmanager] +server=localhost ; server to connect to +port=5038 ; default manager port +username=me_and_only_me ; username for login +secret=i_am_not_telling ; password for login + +[fastagi] +setuid=true ; drop privileges to owner of script +basedir=/var/lib/asterisk/agi-bin/ ; path to script folder + +[festival] ; text to speech engine +text2wave=/usr/bin/text2wave ; path to text2wave binary + +[cepstral] ; alternate text to speech engine +swift=/opt/swift/bin/swift ; path to switft binary +voice=David ; default voice + diff --git a/mkdocs.php b/mkdocs.php index 8ed9e8f..e859a7b 100755 --- a/mkdocs.php +++ b/mkdocs.php @@ -1,7 +1,7 @@ -#!/usr/bin/env php - - - +#!/usr/bin/env php + + + diff --git a/src/phpagi-asmanager.php b/src/phpagi-asmanager.php index e2e7c2f..29e5885 100644 --- a/src/phpagi-asmanager.php +++ b/src/phpagi-asmanager.php @@ -1,975 +1,975 @@ -, David Eder and others - * All Rights Reserved. - * - * This software is released under the terms of the GNU Lesser General Public License v2.1 - * A copy of which is available from http://www.gnu.org/copyleft/lesser.html - * - * We would be happy to list your phpagi based application on the phpagi - * website. Drop me an Email if you'd like us to list your program. - * - * @package phpAGI - * @version 2.0 - */ - - - /** - * Written for PHP 4.3.4, should work with older PHP 4.x versions. - * Please submit bug reports, patches, etc to https://github.com/welltime/phpagi - * - */ - - if(!class_exists('AGI')) - { - require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); - } - - /** - * Asterisk Manager class - * - * @link http://www.voip-info.org/wiki-Asterisk+config+manager.conf - * @link http://www.voip-info.org/wiki-Asterisk+manager+API - * @example examples/sip_show_peer.php Get information about a sip peer - * @package phpAGI - */ - class AGI_AsteriskManager - { - /** - * Config variables - * - * @var array - * @access public - */ - public $config; - - /** - * Socket - * - * @access public - */ - public $socket = null; - - /** - * Server we are connected to - * - * @access public - * @var string - */ - public $server; - - /** - * Port on the server we are connected to - * - * @access public - * @var integer - */ - public $port; - - /** - * Parent AGI - * - * @access private - * @var AGI - */ - public $pagi = false; - - /** - * Event Handlers - * - * @access private - * @var array - */ - private $event_handlers; - - private $_buffer = null; - - /** - * Whether we're successfully logged in - * - * @access private - * @var boolean - */ - private $_logged_in = false; - - public function setPagi(&$agi) - { - $this->pagi = $agi; - } - - /** - * Constructor - * - * @param string $config is the name of the config file to parse or a parent agi from which to read the config - * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['asmanager'] - */ - function __construct($config=null, $optconfig=array()) - { - // load config - if(!is_null($config) && file_exists($config)) - $this->config = parse_ini_file($config, true); - elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) - $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); - - // If optconfig is specified, stuff vals and vars into 'asmanager' config array. - foreach($optconfig as $var=>$val) - $this->config['asmanager'][$var] = $val; - - // add default values to config for uninitialized values - if(!isset($this->config['asmanager']['server'])) $this->config['asmanager']['server'] = 'localhost'; - if(!isset($this->config['asmanager']['port'])) $this->config['asmanager']['port'] = 5038; - if(!isset($this->config['asmanager']['username'])) $this->config['asmanager']['username'] = 'phpagi'; - if(!isset($this->config['asmanager']['secret'])) $this->config['asmanager']['secret'] = 'phpagi'; - if(!isset($this->config['asmanager']['write_log'])) $this->config['asmanager']['write_log'] = false; - } - - /** - * Send a request - * - * @param string $action - * @param array $parameters - * @return array of parameters - */ - function send_request($action, $parameters=array()) - { - $req = "Action: $action\r\n"; - $actionid = null; - foreach ($parameters as $var=>$val) { - if (is_array($val)) { - foreach ($val as $line) { - $req .= "$var: $line\r\n"; - } - } else { - $req .= "$var: $val\r\n"; - if (strtolower($var) == "actionid") { - $actionid = $val; - } - } - } - if (!$actionid) { - $actionid = $this->ActionID(); - $req .= "ActionID: $actionid\r\n"; - } - $req .= "\r\n"; - - fwrite($this->socket, $req); - - return $this->wait_response(false, $actionid); - } - - function read_one_msg($allow_timeout = false) - { - $type = null; - - do { - $buf = fgets($this->socket, 4096); - if (false === $buf) { - throw new Exception("Error reading from AMI socket"); - } - $this->_buffer .= $buf; - - $pos = strpos($this->_buffer, "\r\n\r\n"); - if (false !== $pos) { - // there's a full message in the buffer - break; - } - } while (!feof($this->socket)); - - $msg = substr($this->_buffer, 0, $pos); - $this->_buffer = substr($this->_buffer, $pos+4); - - $msgarr = explode("\r\n", $msg); - - $parameters = array(); - - $r = explode(': ', $msgarr[0]); - $type = strtolower($r[0]); - - if ($r[1] == 'Success' || $r[1] == 'Follows') { - $m = explode(': ', $msgarr[2]); - $msgarr_tmp = $msgarr; - $str = array_pop($msgarr); - $lastline = strpos($str, '--END COMMAND--'); - if (false !== $lastline) { - $parameters['data'] = substr($str, 0, $lastline-1); // cut '\n' too - } else { - if ($m[1] == 'Command output follows') { - $n = 3; - $c = count($msgarr_tmp) - 1; - $output = explode(': ', $msgarr_tmp[3]); - if ($output[1]) { - $data = $output[1]; - while ($n++<$c) { - $output = explode(': ', $msgarr_tmp[$n]); - if ($output[1]) { - $data .= "\n".$output[1]; - } - } - $parameters['data'] = $data; - } - } - } - } - - foreach ($msgarr as $num=>$str) { - $kv = explode(':', $str, 2); - if (!isset($kv[1])) { - $kv[1] = ""; - } - $key = trim($kv[0]); - $val = trim($kv[1]); - $parameters[$key] = $val; - } - - // process response - switch($type) - { - case '': // timeout occured - $timeout = $allow_timeout; - break; - case 'event': - $this->process_event($parameters); - break; - case 'response': - break; - default: - $this->log('Unhandled response packet from Manager: ' . print_r($parameters, true)); - break; - } - - return $parameters; - } - - /** - * Wait for a response - * - * If a request was just sent, this will return the response. - * Otherwise, it will loop forever, handling events. - * - * XXX this code is slightly better then the original one - * however it's still totally screwed up and needs to be rewritten, - * for two reasons at least: - * 1. it does not handle socket errors in any way - * 2. it is terribly synchronous, esp. with eventlists, - * i.e. your code is blocked on waiting until full responce is received - * - * @param boolean $allow_timeout if the socket times out, return an empty array - * @return array of parameters, empty on timeout - */ - function wait_response($allow_timeout = false, $actionid = null) - { - $res = array(); - if ($actionid) { - do { - $res = $this->read_one_msg($allow_timeout); - } while (!( isset($res['ActionID']) && $res['ActionID']==$actionid )); - } else { - $res = $this->read_one_msg($allow_timeout); - return $res; - } - - if (isset($res['EventList']) && $res['EventList']=='start') { - $evlist = array(); - do { - $res = $this->wait_response(false, $actionid); - if (isset($res['EventList']) && $res['EventList']=='Complete') - break; - else - $evlist[] = $res; - } while(true); - $res['events'] = $evlist; - } - - return $res; - } - - - /** - * Connect to Asterisk - * - * @example examples/sip_show_peer.php Get information about a sip peer - * - * @param string $server - * @param string $username - * @param string $secret - * @return boolean true on success - */ - function connect($server=null, $username=null, $secret=null) - { - // use config if not specified - if(is_null($server)) $server = $this->config['asmanager']['server']; - if(is_null($username)) $username = $this->config['asmanager']['username']; - if(is_null($secret)) $secret = $this->config['asmanager']['secret']; - - // get port from server if specified - if(strpos($server, ':') !== false) - { - $c = explode(':', $server); - $this->server = $c[0]; - $this->port = $c[1]; - } - else - { - $this->server = $server; - $this->port = $this->config['asmanager']['port']; - } - - // connect the socket - $errno = $errstr = null; - $this->socket = @fsockopen($this->server, $this->port, $errno, $errstr); - if($this->socket == false) - { - $this->log("Unable to connect to manager {$this->server}:{$this->port} ($errno): $errstr"); - return false; - } - - // read the header - $str = fgets($this->socket); - if($str == false) - { - // a problem. - $this->log("Asterisk Manager header not received."); - return false; - } - else - { - // note: don't $this->log($str) until someone looks to see why it mangles the logging - } - - // login - $res = $this->send_request('login', array('Username'=>$username, 'Secret'=>$secret)); - if($res['Response'] != 'Success') - { - $this->_logged_in = false; - $this->log("Failed to login."); - $this->disconnect(); - return false; - } - $this->_logged_in = true; - return true; - } - - /** - * Disconnect - * - * @example examples/sip_show_peer.php Get information about a sip peer - */ - function disconnect() - { - if($this->_logged_in==true) - $this->logoff(); - fclose($this->socket); - } - - // ********************************************************************************************************* - // ** COMMANDS ** - // ********************************************************************************************************* - - /** - * Set Absolute Timeout - * - * Hangup a channel after a certain time. - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+AbsoluteTimeout - * @param string $channel Channel name to hangup - * @param integer $timeout Maximum duration of the call (sec) - */ - function AbsoluteTimeout($channel, $timeout) - { - return $this->send_request('AbsoluteTimeout', array('Channel'=>$channel, 'Timeout'=>$timeout)); - } - - /** - * Change monitoring filename of a channel - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ChangeMonitor - * @param string $channel the channel to record. - * @param string $file the new name of the file created in the monitor spool directory. - */ - function ChangeMonitor($channel, $file) - { - return $this->send_request('ChangeMontior', array('Channel'=>$channel, 'File'=>$file)); - } - - /** - * Execute Command - * - * @example examples/sip_show_peer.php Get information about a sip peer - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Command - * @link http://www.voip-info.org/wiki-Asterisk+CLI - * @param string $command - * @param string $actionid message matching variable - */ - function Command($command, $actionid=null) - { - $parameters = array('Command'=>$command); - if($actionid) $parameters['ActionID'] = $actionid; - return $this->send_request('Command', $parameters); - } - - /** - * Enable/Disable sending of events to this manager - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Events - * @param string $eventmask is either 'on', 'off', or 'system,call,log' - */ - function Events($eventmask) - { - return $this->send_request('Events', array('EventMask'=>$eventmask)); - } - - /** - * Generate random ActionID - **/ - function ActionID() - { - return "A".sprintf(rand(),"%6d"); - } - - /** - * - * DBGet - * http://www.voip-info.org/wiki/index.php?page=Asterisk+Manager+API+Action+DBGet - * @param string $family key family - * @param string $key key name - **/ - function DBGet($family, $key, $actionid = null) - { - $parameters = array('Family'=>$family, 'Key'=>$key); - if($actionid == null) - $actionid = $this->ActionID(); - $parameters['ActionID'] = $actionid; - $response = $this->send_request("DBGet", $parameters); - if($response['Response'] == "Success") - { - $response = $this->wait_response(false, $actionid); - return $response['Val']; - } - return ""; - } - - /** - * Check Extension Status - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ExtensionState - * @param string $exten Extension to check state on - * @param string $context Context for extension - * @param string $actionid message matching variable - */ - function ExtensionState($exten, $context, $actionid=null) - { - $parameters = array('Exten'=>$exten, 'Context'=>$context); - if($actionid) $parameters['ActionID'] = $actionid; - return $this->send_request('ExtensionState', $parameters); - } - - /** - * Gets a Channel Variable - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+GetVar - * @link http://www.voip-info.org/wiki-Asterisk+variables - * @param string $channel Channel to read variable from - * @param string $variable - * @param string $actionid message matching variable - */ - function GetVar($channel, $variable, $actionid=null) - { - $parameters = array('Channel'=>$channel, 'Variable'=>$variable); - if($actionid) $parameters['ActionID'] = $actionid; - return $this->send_request('GetVar', $parameters); - } - - /** - * Hangup Channel - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Hangup - * @param string $channel The channel name to be hungup - */ - function Hangup($channel) - { - return $this->send_request('Hangup', array('Channel'=>$channel)); - } - - /** - * List IAX Peers - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+IAXpeers - */ - function IAXPeers() - { - return $this->send_request('IAXPeers'); - } - - /** - * List available manager commands - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ListCommands - * @param string $actionid message matching variable - */ - function ListCommands($actionid=null) - { - if($actionid) - return $this->send_request('ListCommands', array('ActionID'=>$actionid)); - else - return $this->send_request('ListCommands'); - } - - /** - * Logoff Manager - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Logoff - */ - function Logoff() - { - return $this->send_request('Logoff'); - } - - /** - * Check Mailbox Message Count - * - * Returns number of new and old messages. - * Message: Mailbox Message Count - * Mailbox: - * NewMessages: - * OldMessages: - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxCount - * @param string $mailbox Full mailbox ID @ - * @param string $actionid message matching variable - */ - function MailboxCount($mailbox, $actionid=null) - { - $parameters = array('Mailbox'=>$mailbox); - if($actionid) $parameters['ActionID'] = $actionid; - return $this->send_request('MailboxCount', $parameters); - } - - /** - * Check Mailbox - * - * Returns number of messages. - * Message: Mailbox Status - * Mailbox: - * Waiting: - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxStatus - * @param string $mailbox Full mailbox ID @ - * @param string $actionid message matching variable - */ - function MailboxStatus($mailbox, $actionid=null) - { - $parameters = array('Mailbox'=>$mailbox); - if($actionid) $parameters['ActionID'] = $actionid; - return $this->send_request('MailboxStatus', $parameters); - } - - /** - * Monitor a channel - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor - * @param string $channel - * @param string $file - * @param string $format - * @param boolean $mix - */ - function Monitor($channel, $file=null, $format=null, $mix=null) - { - $parameters = array('Channel'=>$channel); - if($file) $parameters['File'] = $file; - if($format) $parameters['Format'] = $format; - if(!is_null($file)) $parameters['Mix'] = ($mix) ? 'true' : 'false'; - return $this->send_request('Monitor', $parameters); - } - - /** - * Originate Call - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Originate - * @param string $channel Channel name to call - * @param string $exten Extension to use (requires 'Context' and 'Priority') - * @param string $context Context to use (requires 'Exten' and 'Priority') - * @param string $priority Priority to use (requires 'Exten' and 'Context') - * @param string $application Application to use - * @param string $data Data to use (requires 'Application') - * @param integer $timeout How long to wait for call to be answered (in ms) - * @param string $callerid Caller ID to be set on the outgoing channel - * @param string $variable Channel variable to set (VAR1=value1|VAR2=value2) - * @param string $account Account code - * @param boolean $async true fast origination - * @param string $actionid message matching variable - */ - function Originate($channel, - $exten=null, $context=null, $priority=null, - $application=null, $data=null, - $timeout=null, $callerid=null, $variable=null, $account=null, $async=null, $actionid=null) - { - $parameters = array('Channel'=>$channel); - - if($exten) $parameters['Exten'] = $exten; - if($context) $parameters['Context'] = $context; - if($priority) $parameters['Priority'] = $priority; - - if($application) $parameters['Application'] = $application; - if($data) $parameters['Data'] = $data; - - if($timeout) $parameters['Timeout'] = $timeout; - if($callerid) $parameters['CallerID'] = $callerid; - if($variable) $parameters['Variable'] = $variable; - if($account) $parameters['Account'] = $account; - if(!is_null($async)) $parameters['Async'] = ($async) ? 'true' : 'false'; - if($actionid) $parameters['ActionID'] = $actionid; - - return $this->send_request('Originate', $parameters); - } - - /** - * List parked calls - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ParkedCalls - * @param string $actionid message matching variable - */ - function ParkedCalls($actionid=null) - { - if($actionid) - return $this->send_request('ParkedCalls', array('ActionID'=>$actionid)); - else - return $this->send_request('ParkedCalls'); - } - - /** - * Ping - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Ping - */ - function Ping() - { - return $this->send_request('Ping'); - } - - /** - * Queue Add - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueAdd - * @param string $queue - * @param string $interface - * @param integer $penalty - * @param string $memberName - */ - function QueueAdd($queue, $interface, $penalty=0, $memberName = false) - { - $parameters = array('Queue'=>$queue, 'Interface'=>$interface); - if($penalty) $parameters['Penalty'] = $penalty; - if($memberName) $parameters["MemberName"] = $memberName; - return $this->send_request('QueueAdd', $parameters); - } - - /** - * Queue Remove - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueRemove - * @param string $queue - * @param string $interface - */ - function QueueRemove($queue, $interface) - { - return $this->send_request('QueueRemove', array('Queue'=>$queue, 'Interface'=>$interface)); - } - - function QueueReload() - { - return $this->send_request('QueueReload'); - } - - /** - * Queues - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Queues - */ - function Queues() - { - return $this->send_request('Queues'); - } - - /** - * Queue Status - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueStatus - * @param string $actionid message matching variable - */ - function QueueStatus($actionid=null) - { - if($actionid) - return $this->send_request('QueueStatus', array('ActionID'=>$actionid)); - else - return $this->send_request('QueueStatus'); - } - - /** - * Redirect - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Redirect - * @param string $channel - * @param string $extrachannel - * @param string $exten - * @param string $context - * @param string $priority - */ - function Redirect($channel, $extrachannel, $exten, $context, $priority) - { - return $this->send_request('Redirect', array('Channel'=>$channel, 'ExtraChannel'=>$extrachannel, 'Exten'=>$exten, - 'Context'=>$context, 'Priority'=>$priority)); - } - - function Atxfer($channel, $exten, $context, $priority) - { - return $this->send_request('Atxfer', array('Channel'=>$channel, 'Exten'=>$exten, - 'Context'=>$context, 'Priority'=>$priority)); - } - - /** - * Set the CDR UserField - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetCDRUserField - * @param string $userfield - * @param string $channel - * @param string $append - */ - function SetCDRUserField($userfield, $channel, $append=null) - { - $parameters = array('UserField'=>$userfield, 'Channel'=>$channel); - if($append) $parameters['Append'] = $append; - return $this->send_request('SetCDRUserField', $parameters); - } - - /** - * Set Channel Variable - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetVar - * @param string $channel Channel to set variable for - * @param string $variable name - * @param string $value - */ - function SetVar($channel, $variable, $value) - { - return $this->send_request('SetVar', array('Channel'=>$channel, 'Variable'=>$variable, 'Value'=>$value)); - } - - /** - * Channel Status - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Status - * @param string $channel - * @param string $actionid message matching variable - */ - function Status($channel, $actionid=null) - { - $parameters = array('Channel'=>$channel); - if($actionid) $parameters['ActionID'] = $actionid; - return $this->send_request('Status', $parameters); - } - - /** - * Stop monitoring a channel - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+StopMonitor - * @param string $channel - */ - function StopMonitor($channel) - { - return $this->send_request('StopMonitor', array('Channel'=>$channel)); - } - - /** - * Dial over Zap channel while offhook - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDialOffhook - * @param string $zapchannel - * @param string $number - */ - function ZapDialOffhook($zapchannel, $number) - { - return $this->send_request('ZapDialOffhook', array('ZapChannel'=>$zapchannel, 'Number'=>$number)); - } - - /** - * Toggle Zap channel Do Not Disturb status OFF - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDoff - * @param string $zapchannel - */ - function ZapDNDoff($zapchannel) - { - return $this->send_request('ZapDNDoff', array('ZapChannel'=>$zapchannel)); - } - - /** - * Toggle Zap channel Do Not Disturb status ON - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDon - * @param string $zapchannel - */ - function ZapDNDon($zapchannel) - { - return $this->send_request('ZapDNDon', array('ZapChannel'=>$zapchannel)); - } - - /** - * Hangup Zap Channel - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapHangup - * @param string $zapchannel - */ - function ZapHangup($zapchannel) - { - return $this->send_request('ZapHangup', array('ZapChannel'=>$zapchannel)); - } - - /** - * Transfer Zap Channel - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapTransfer - * @param string $zapchannel - */ - function ZapTransfer($zapchannel) - { - return $this->send_request('ZapTransfer', array('ZapChannel'=>$zapchannel)); - } - - /** - * Zap Show Channels - * - * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapShowChannels - * @param string $actionid message matching variable - */ - function ZapShowChannels($actionid=null) - { - if($actionid) - return $this->send_request('ZapShowChannels', array('ActionID'=>$actionid)); - else - return $this->send_request('ZapShowChannels'); - } - - // ********************************************************************************************************* - // ** MISC ** - // ********************************************************************************************************* - - /* - * Log a message - * - * @param string $message - * @param integer $level from 1 to 4 - */ - function log($message, $level=1) - { - if($this->pagi != false) - $this->pagi->conlog($message, $level); - elseif($this->config['asmanager']['write_log']) - error_log(date('r') . ' - ' . $message); - } - - /** - * Add event handler - * - * Known Events include ( http://www.voip-info.org/wiki-asterisk+manager+events ) - * Link - Fired when two voice channels are linked together and voice data exchange commences. - * Unlink - Fired when a link between two voice channels is discontinued, for example, just before call completion. - * Newexten - - * Hangup - - * Newchannel - - * Newstate - - * Reload - Fired when the "RELOAD" console command is executed. - * Shutdown - - * ExtensionStatus - - * Rename - - * Newcallerid - - * Alarm - - * AlarmClear - - * Agentcallbacklogoff - - * Agentcallbacklogin - - * Agentlogoff - - * MeetmeJoin - - * MessageWaiting - - * join - - * leave - - * AgentCalled - - * ParkedCall - Fired after ParkedCalls - * Cdr - - * ParkedCallsComplete - - * QueueParams - - * QueueMember - - * QueueStatusEnd - - * Status - - * StatusComplete - - * ZapShowChannels - Fired after ZapShowChannels - * ZapShowChannelsComplete - - * - * @param string $event type or * for default handler - * @param string $callback function - * @return boolean sucess - */ - function add_event_handler($event, $callback) - { - $event = strtolower($event); - if(isset($this->event_handlers[$event])) - { - $this->log("$event handler is already defined, not over-writing."); - return false; - } - $this->event_handlers[$event] = $callback; - return true; - } - /** - * - * Remove event handler - * - * @param string $event type or * for default handler - * @return boolean sucess - **/ - function remove_event_handler($event) - { - $event = strtolower($event); - if(isset($this->event_handlers[$event])) - { - unset($this->event_handlers[$event]); - return true; - } - $this->log("$event handler is not defined."); - return false; - } - - /** - * Process event - * - * @access private - * @param array $parameters - * @return mixed result of event handler or false if no handler was found - */ - function process_event($parameters) - { - $ret = false; - $e = strtolower($parameters['Event']); - $this->log("Got event.. $e"); - - $handler = ''; - if(isset($this->event_handlers[$e])) $handler = $this->event_handlers[$e]; - elseif(isset($this->event_handlers['*'])) $handler = $this->event_handlers['*']; - - if(function_exists($handler)) - { - $this->log("Execute handler $handler"); - $ret = $handler($e, $parameters, $this->server, $this->port); - } elseif (is_array($handler)) { - $ret = call_user_func($handler, $e, $parameters, $this->server, $this->port); - } - else - $this->log("No event handler for event '$e'"); - return $ret; - } - } -?> +, David Eder and others + * All Rights Reserved. + * + * This software is released under the terms of the GNU Lesser General Public License v2.1 + * A copy of which is available from http://www.gnu.org/copyleft/lesser.html + * + * We would be happy to list your phpagi based application on the phpagi + * website. Drop me an Email if you'd like us to list your program. + * + * @package phpAGI + * @version 2.0 + */ + + + /** + * Written for PHP 4.3.4, should work with older PHP 4.x versions. + * Please submit bug reports, patches, etc to https://github.com/ggets/phpagi + * + */ + + if(!class_exists('AGI')) + { + require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); + } + + /** + * Asterisk Manager class + * + * @link http://www.voip-info.org/wiki-Asterisk+config+manager.conf + * @link http://www.voip-info.org/wiki-Asterisk+manager+API + * @example examples/sip_show_peer.php Get information about a sip peer + * @package phpAGI + */ + class AGI_AsteriskManager + { + /** + * Config variables + * + * @var array + * @access public + */ + public $config; + + /** + * Socket + * + * @access public + */ + public $socket = null; + + /** + * Server we are connected to + * + * @access public + * @var string + */ + public $server; + + /** + * Port on the server we are connected to + * + * @access public + * @var integer + */ + public $port; + + /** + * Parent AGI + * + * @access private + * @var AGI + */ + public $pagi = false; + + /** + * Event Handlers + * + * @access private + * @var array + */ + private $event_handlers; + + private $_buffer = null; + + /** + * Whether we're successfully logged in + * + * @access private + * @var boolean + */ + private $_logged_in = false; + + public function setPagi(&$agi) + { + $this->pagi = $agi; + } + + /** + * Constructor + * + * @param string $config is the name of the config file to parse or a parent agi from which to read the config + * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['asmanager'] + */ + function __construct($config=null, $optconfig=array()) + { + // load config + if(!is_null($config) && file_exists($config)) + $this->config = parse_ini_file($config, true); + elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) + $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); + + // If optconfig is specified, stuff vals and vars into 'asmanager' config array. + foreach($optconfig as $var=>$val) + $this->config['asmanager'][$var] = $val; + + // add default values to config for uninitialized values + if(!isset($this->config['asmanager']['server'])) $this->config['asmanager']['server'] = 'localhost'; + if(!isset($this->config['asmanager']['port'])) $this->config['asmanager']['port'] = 5038; + if(!isset($this->config['asmanager']['username'])) $this->config['asmanager']['username'] = 'phpagi'; + if(!isset($this->config['asmanager']['secret'])) $this->config['asmanager']['secret'] = 'phpagi'; + if(!isset($this->config['asmanager']['write_log'])) $this->config['asmanager']['write_log'] = false; + } + + /** + * Send a request + * + * @param string $action + * @param array $parameters + * @return array of parameters + */ + function send_request($action, $parameters=array()) + { + $req = "Action: $action\r\n"; + $actionid = null; + foreach ($parameters as $var=>$val) { + if (is_array($val)) { + foreach ($val as $line) { + $req .= "$var: $line\r\n"; + } + } else { + $req .= "$var: $val\r\n"; + if (strtolower($var) == "actionid") { + $actionid = $val; + } + } + } + if (!$actionid) { + $actionid = $this->ActionID(); + $req .= "ActionID: $actionid\r\n"; + } + $req .= "\r\n"; + + fwrite($this->socket, $req); + + return $this->wait_response(false, $actionid); + } + + function read_one_msg($allow_timeout = false) + { + $type = null; + + do { + $buf = fgets($this->socket, 4096); + if (false === $buf) { + throw new Exception("Error reading from AMI socket"); + } + $this->_buffer .= $buf; + + $pos = strpos($this->_buffer, "\r\n\r\n"); + if (false !== $pos) { + // there's a full message in the buffer + break; + } + } while (!feof($this->socket)); + + $msg = substr($this->_buffer, 0, $pos); + $this->_buffer = substr($this->_buffer, $pos+4); + + $msgarr = explode("\r\n", $msg); + + $parameters = array(); + + $r = explode(': ', $msgarr[0]); + $type = strtolower($r[0]); + + if ($r[1] == 'Success' || $r[1] == 'Follows') { + $m = explode(': ', $msgarr[2]); + $msgarr_tmp = $msgarr; + $str = array_pop($msgarr); + $lastline = strpos($str, '--END COMMAND--'); + if (false !== $lastline) { + $parameters['data'] = substr($str, 0, $lastline-1); // cut '\n' too + } else { + if ($m[1] == 'Command output follows') { + $n = 3; + $c = count($msgarr_tmp) - 1; + $output = explode(': ', $msgarr_tmp[3]); + if ($output[1]) { + $data = $output[1]; + while ($n++<$c) { + $output = explode(': ', $msgarr_tmp[$n]); + if ($output[1]) { + $data .= "\n".$output[1]; + } + } + $parameters['data'] = $data; + } + } + } + } + + foreach ($msgarr as $num=>$str) { + $kv = explode(':', $str, 2); + if (!isset($kv[1])) { + $kv[1] = ""; + } + $key = trim($kv[0]); + $val = trim($kv[1]); + $parameters[$key] = $val; + } + + // process response + switch($type) + { + case '': // timeout occured + $timeout = $allow_timeout; + break; + case 'event': + $this->process_event($parameters); + break; + case 'response': + break; + default: + $this->log('Unhandled response packet from Manager: ' . print_r($parameters, true)); + break; + } + + return $parameters; + } + + /** + * Wait for a response + * + * If a request was just sent, this will return the response. + * Otherwise, it will loop forever, handling events. + * + * XXX this code is slightly better then the original one + * however it's still totally screwed up and needs to be rewritten, + * for two reasons at least: + * 1. it does not handle socket errors in any way + * 2. it is terribly synchronous, esp. with eventlists, + * i.e. your code is blocked on waiting until full responce is received + * + * @param boolean $allow_timeout if the socket times out, return an empty array + * @return array of parameters, empty on timeout + */ + function wait_response($allow_timeout = false, $actionid = null) + { + $res = array(); + if ($actionid) { + do { + $res = $this->read_one_msg($allow_timeout); + } while (!( isset($res['ActionID']) && $res['ActionID']==$actionid )); + } else { + $res = $this->read_one_msg($allow_timeout); + return $res; + } + + if (isset($res['EventList']) && $res['EventList']=='start') { + $evlist = array(); + do { + $res = $this->wait_response(false, $actionid); + if (isset($res['EventList']) && $res['EventList']=='Complete') + break; + else + $evlist[] = $res; + } while(true); + $res['events'] = $evlist; + } + + return $res; + } + + + /** + * Connect to Asterisk + * + * @example examples/sip_show_peer.php Get information about a sip peer + * + * @param string $server + * @param string $username + * @param string $secret + * @return boolean true on success + */ + function connect($server=null, $username=null, $secret=null) + { + // use config if not specified + if(is_null($server)) $server = $this->config['asmanager']['server']; + if(is_null($username)) $username = $this->config['asmanager']['username']; + if(is_null($secret)) $secret = $this->config['asmanager']['secret']; + + // get port from server if specified + if(strpos($server, ':') !== false) + { + $c = explode(':', $server); + $this->server = $c[0]; + $this->port = $c[1]; + } + else + { + $this->server = $server; + $this->port = $this->config['asmanager']['port']; + } + + // connect the socket + $errno = $errstr = null; + $this->socket = @fsockopen($this->server, $this->port, $errno, $errstr); + if($this->socket == false) + { + $this->log("Unable to connect to manager {$this->server}:{$this->port} ($errno): $errstr"); + return false; + } + + // read the header + $str = fgets($this->socket); + if($str == false) + { + // a problem. + $this->log("Asterisk Manager header not received."); + return false; + } + else + { + // note: don't $this->log($str) until someone looks to see why it mangles the logging + } + + // login + $res = $this->send_request('login', array('Username'=>$username, 'Secret'=>$secret)); + if($res['Response'] != 'Success') + { + $this->_logged_in = false; + $this->log("Failed to login."); + $this->disconnect(); + return false; + } + $this->_logged_in = true; + return true; + } + + /** + * Disconnect + * + * @example examples/sip_show_peer.php Get information about a sip peer + */ + function disconnect() + { + if($this->_logged_in==true) + $this->logoff(); + fclose($this->socket); + } + + // ********************************************************************************************************* + // ** COMMANDS ** + // ********************************************************************************************************* + + /** + * Set Absolute Timeout + * + * Hangup a channel after a certain time. + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+AbsoluteTimeout + * @param string $channel Channel name to hangup + * @param integer $timeout Maximum duration of the call (sec) + */ + function AbsoluteTimeout($channel, $timeout) + { + return $this->send_request('AbsoluteTimeout', array('Channel'=>$channel, 'Timeout'=>$timeout)); + } + + /** + * Change monitoring filename of a channel + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ChangeMonitor + * @param string $channel the channel to record. + * @param string $file the new name of the file created in the monitor spool directory. + */ + function ChangeMonitor($channel, $file) + { + return $this->send_request('ChangeMontior', array('Channel'=>$channel, 'File'=>$file)); + } + + /** + * Execute Command + * + * @example examples/sip_show_peer.php Get information about a sip peer + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Command + * @link http://www.voip-info.org/wiki-Asterisk+CLI + * @param string $command + * @param string $actionid message matching variable + */ + function Command($command, $actionid=null) + { + $parameters = array('Command'=>$command); + if($actionid) $parameters['ActionID'] = $actionid; + return $this->send_request('Command', $parameters); + } + + /** + * Enable/Disable sending of events to this manager + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Events + * @param string $eventmask is either 'on', 'off', or 'system,call,log' + */ + function Events($eventmask) + { + return $this->send_request('Events', array('EventMask'=>$eventmask)); + } + + /** + * Generate random ActionID + **/ + function ActionID() + { + return "A".sprintf(rand(),"%6d"); + } + + /** + * + * DBGet + * http://www.voip-info.org/wiki/index.php?page=Asterisk+Manager+API+Action+DBGet + * @param string $family key family + * @param string $key key name + **/ + function DBGet($family, $key, $actionid = null) + { + $parameters = array('Family'=>$family, 'Key'=>$key); + if($actionid == null) + $actionid = $this->ActionID(); + $parameters['ActionID'] = $actionid; + $response = $this->send_request("DBGet", $parameters); + if($response['Response'] == "Success") + { + $response = $this->wait_response(false, $actionid); + return $response['Val']; + } + return ""; + } + + /** + * Check Extension Status + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ExtensionState + * @param string $exten Extension to check state on + * @param string $context Context for extension + * @param string $actionid message matching variable + */ + function ExtensionState($exten, $context, $actionid=null) + { + $parameters = array('Exten'=>$exten, 'Context'=>$context); + if($actionid) $parameters['ActionID'] = $actionid; + return $this->send_request('ExtensionState', $parameters); + } + + /** + * Gets a Channel Variable + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+GetVar + * @link http://www.voip-info.org/wiki-Asterisk+variables + * @param string $channel Channel to read variable from + * @param string $variable + * @param string $actionid message matching variable + */ + function GetVar($channel, $variable, $actionid=null) + { + $parameters = array('Channel'=>$channel, 'Variable'=>$variable); + if($actionid) $parameters['ActionID'] = $actionid; + return $this->send_request('GetVar', $parameters); + } + + /** + * Hangup Channel + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Hangup + * @param string $channel The channel name to be hungup + */ + function Hangup($channel) + { + return $this->send_request('Hangup', array('Channel'=>$channel)); + } + + /** + * List IAX Peers + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+IAXpeers + */ + function IAXPeers() + { + return $this->send_request('IAXPeers'); + } + + /** + * List available manager commands + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ListCommands + * @param string $actionid message matching variable + */ + function ListCommands($actionid=null) + { + if($actionid) + return $this->send_request('ListCommands', array('ActionID'=>$actionid)); + else + return $this->send_request('ListCommands'); + } + + /** + * Logoff Manager + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Logoff + */ + function Logoff() + { + return $this->send_request('Logoff'); + } + + /** + * Check Mailbox Message Count + * + * Returns number of new and old messages. + * Message: Mailbox Message Count + * Mailbox: + * NewMessages: + * OldMessages: + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxCount + * @param string $mailbox Full mailbox ID @ + * @param string $actionid message matching variable + */ + function MailboxCount($mailbox, $actionid=null) + { + $parameters = array('Mailbox'=>$mailbox); + if($actionid) $parameters['ActionID'] = $actionid; + return $this->send_request('MailboxCount', $parameters); + } + + /** + * Check Mailbox + * + * Returns number of messages. + * Message: Mailbox Status + * Mailbox: + * Waiting: + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxStatus + * @param string $mailbox Full mailbox ID @ + * @param string $actionid message matching variable + */ + function MailboxStatus($mailbox, $actionid=null) + { + $parameters = array('Mailbox'=>$mailbox); + if($actionid) $parameters['ActionID'] = $actionid; + return $this->send_request('MailboxStatus', $parameters); + } + + /** + * Monitor a channel + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor + * @param string $channel + * @param string $file + * @param string $format + * @param boolean $mix + */ + function Monitor($channel, $file=null, $format=null, $mix=null) + { + $parameters = array('Channel'=>$channel); + if($file) $parameters['File'] = $file; + if($format) $parameters['Format'] = $format; + if(!is_null($file)) $parameters['Mix'] = ($mix) ? 'true' : 'false'; + return $this->send_request('Monitor', $parameters); + } + + /** + * Originate Call + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Originate + * @param string $channel Channel name to call + * @param string $exten Extension to use (requires 'Context' and 'Priority') + * @param string $context Context to use (requires 'Exten' and 'Priority') + * @param string $priority Priority to use (requires 'Exten' and 'Context') + * @param string $application Application to use + * @param string $data Data to use (requires 'Application') + * @param integer $timeout How long to wait for call to be answered (in ms) + * @param string $callerid Caller ID to be set on the outgoing channel + * @param string $variable Channel variable to set (VAR1=value1|VAR2=value2) + * @param string $account Account code + * @param boolean $async true fast origination + * @param string $actionid message matching variable + */ + function Originate($channel, + $exten=null, $context=null, $priority=null, + $application=null, $data=null, + $timeout=null, $callerid=null, $variable=null, $account=null, $async=null, $actionid=null) + { + $parameters = array('Channel'=>$channel); + + if($exten) $parameters['Exten'] = $exten; + if($context) $parameters['Context'] = $context; + if($priority) $parameters['Priority'] = $priority; + + if($application) $parameters['Application'] = $application; + if($data) $parameters['Data'] = $data; + + if($timeout) $parameters['Timeout'] = $timeout; + if($callerid) $parameters['CallerID'] = $callerid; + if($variable) $parameters['Variable'] = $variable; + if($account) $parameters['Account'] = $account; + if(!is_null($async)) $parameters['Async'] = ($async) ? 'true' : 'false'; + if($actionid) $parameters['ActionID'] = $actionid; + + return $this->send_request('Originate', $parameters); + } + + /** + * List parked calls + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ParkedCalls + * @param string $actionid message matching variable + */ + function ParkedCalls($actionid=null) + { + if($actionid) + return $this->send_request('ParkedCalls', array('ActionID'=>$actionid)); + else + return $this->send_request('ParkedCalls'); + } + + /** + * Ping + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Ping + */ + function Ping() + { + return $this->send_request('Ping'); + } + + /** + * Queue Add + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueAdd + * @param string $queue + * @param string $interface + * @param integer $penalty + * @param string $memberName + */ + function QueueAdd($queue, $interface, $penalty=0, $memberName = false) + { + $parameters = array('Queue'=>$queue, 'Interface'=>$interface); + if($penalty) $parameters['Penalty'] = $penalty; + if($memberName) $parameters["MemberName"] = $memberName; + return $this->send_request('QueueAdd', $parameters); + } + + /** + * Queue Remove + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueRemove + * @param string $queue + * @param string $interface + */ + function QueueRemove($queue, $interface) + { + return $this->send_request('QueueRemove', array('Queue'=>$queue, 'Interface'=>$interface)); + } + + function QueueReload() + { + return $this->send_request('QueueReload'); + } + + /** + * Queues + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Queues + */ + function Queues() + { + return $this->send_request('Queues'); + } + + /** + * Queue Status + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueStatus + * @param string $actionid message matching variable + */ + function QueueStatus($actionid=null) + { + if($actionid) + return $this->send_request('QueueStatus', array('ActionID'=>$actionid)); + else + return $this->send_request('QueueStatus'); + } + + /** + * Redirect + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Redirect + * @param string $channel + * @param string $extrachannel + * @param string $exten + * @param string $context + * @param string $priority + */ + function Redirect($channel, $extrachannel, $exten, $context, $priority) + { + return $this->send_request('Redirect', array('Channel'=>$channel, 'ExtraChannel'=>$extrachannel, 'Exten'=>$exten, + 'Context'=>$context, 'Priority'=>$priority)); + } + + function Atxfer($channel, $exten, $context, $priority) + { + return $this->send_request('Atxfer', array('Channel'=>$channel, 'Exten'=>$exten, + 'Context'=>$context, 'Priority'=>$priority)); + } + + /** + * Set the CDR UserField + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetCDRUserField + * @param string $userfield + * @param string $channel + * @param string $append + */ + function SetCDRUserField($userfield, $channel, $append=null) + { + $parameters = array('UserField'=>$userfield, 'Channel'=>$channel); + if($append) $parameters['Append'] = $append; + return $this->send_request('SetCDRUserField', $parameters); + } + + /** + * Set Channel Variable + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetVar + * @param string $channel Channel to set variable for + * @param string $variable name + * @param string $value + */ + function SetVar($channel, $variable, $value) + { + return $this->send_request('SetVar', array('Channel'=>$channel, 'Variable'=>$variable, 'Value'=>$value)); + } + + /** + * Channel Status + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Status + * @param string $channel + * @param string $actionid message matching variable + */ + function Status($channel, $actionid=null) + { + $parameters = array('Channel'=>$channel); + if($actionid) $parameters['ActionID'] = $actionid; + return $this->send_request('Status', $parameters); + } + + /** + * Stop monitoring a channel + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+StopMonitor + * @param string $channel + */ + function StopMonitor($channel) + { + return $this->send_request('StopMonitor', array('Channel'=>$channel)); + } + + /** + * Dial over Zap channel while offhook + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDialOffhook + * @param string $zapchannel + * @param string $number + */ + function ZapDialOffhook($zapchannel, $number) + { + return $this->send_request('ZapDialOffhook', array('ZapChannel'=>$zapchannel, 'Number'=>$number)); + } + + /** + * Toggle Zap channel Do Not Disturb status OFF + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDoff + * @param string $zapchannel + */ + function ZapDNDoff($zapchannel) + { + return $this->send_request('ZapDNDoff', array('ZapChannel'=>$zapchannel)); + } + + /** + * Toggle Zap channel Do Not Disturb status ON + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDon + * @param string $zapchannel + */ + function ZapDNDon($zapchannel) + { + return $this->send_request('ZapDNDon', array('ZapChannel'=>$zapchannel)); + } + + /** + * Hangup Zap Channel + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapHangup + * @param string $zapchannel + */ + function ZapHangup($zapchannel) + { + return $this->send_request('ZapHangup', array('ZapChannel'=>$zapchannel)); + } + + /** + * Transfer Zap Channel + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapTransfer + * @param string $zapchannel + */ + function ZapTransfer($zapchannel) + { + return $this->send_request('ZapTransfer', array('ZapChannel'=>$zapchannel)); + } + + /** + * Zap Show Channels + * + * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapShowChannels + * @param string $actionid message matching variable + */ + function ZapShowChannels($actionid=null) + { + if($actionid) + return $this->send_request('ZapShowChannels', array('ActionID'=>$actionid)); + else + return $this->send_request('ZapShowChannels'); + } + + // ********************************************************************************************************* + // ** MISC ** + // ********************************************************************************************************* + + /* + * Log a message + * + * @param string $message + * @param integer $level from 1 to 4 + */ + function log($message, $level=1) + { + if($this->pagi != false) + $this->pagi->conlog($message, $level); + elseif($this->config['asmanager']['write_log']) + error_log(date('r') . ' - ' . $message); + } + + /** + * Add event handler + * + * Known Events include ( http://www.voip-info.org/wiki-asterisk+manager+events ) + * Link - Fired when two voice channels are linked together and voice data exchange commences. + * Unlink - Fired when a link between two voice channels is discontinued, for example, just before call completion. + * Newexten - + * Hangup - + * Newchannel - + * Newstate - + * Reload - Fired when the "RELOAD" console command is executed. + * Shutdown - + * ExtensionStatus - + * Rename - + * Newcallerid - + * Alarm - + * AlarmClear - + * Agentcallbacklogoff - + * Agentcallbacklogin - + * Agentlogoff - + * MeetmeJoin - + * MessageWaiting - + * join - + * leave - + * AgentCalled - + * ParkedCall - Fired after ParkedCalls + * Cdr - + * ParkedCallsComplete - + * QueueParams - + * QueueMember - + * QueueStatusEnd - + * Status - + * StatusComplete - + * ZapShowChannels - Fired after ZapShowChannels + * ZapShowChannelsComplete - + * + * @param string $event type or * for default handler + * @param string $callback function + * @return boolean sucess + */ + function add_event_handler($event, $callback) + { + $event = strtolower($event); + if(isset($this->event_handlers[$event])) + { + $this->log("$event handler is already defined, not over-writing."); + return false; + } + $this->event_handlers[$event] = $callback; + return true; + } + /** + * + * Remove event handler + * + * @param string $event type or * for default handler + * @return boolean sucess + **/ + function remove_event_handler($event) + { + $event = strtolower($event); + if(isset($this->event_handlers[$event])) + { + unset($this->event_handlers[$event]); + return true; + } + $this->log("$event handler is not defined."); + return false; + } + + /** + * Process event + * + * @access private + * @param array $parameters + * @return mixed result of event handler or false if no handler was found + */ + function process_event($parameters) + { + $ret = false; + $e = strtolower($parameters['Event']); + $this->log("Got event.. $e"); + + $handler = ''; + if(isset($this->event_handlers[$e])) $handler = $this->event_handlers[$e]; + elseif(isset($this->event_handlers['*'])) $handler = $this->event_handlers['*']; + + if(function_exists($handler)) + { + $this->log("Execute handler $handler"); + $ret = $handler($e, $parameters, $this->server, $this->port); + } elseif (is_array($handler)) { + $ret = call_user_func($handler, $e, $parameters, $this->server, $this->port); + } + else + $this->log("No event handler for event '$e'"); + return $ret; + } + } +?> diff --git a/src/phpagi-fastagi.php b/src/phpagi-fastagi.php index f0a724a..92bc2e2 100755 --- a/src/phpagi-fastagi.php +++ b/src/phpagi-fastagi.php @@ -1,79 +1,79 @@ -#!/usr/local/bin/php -q -, David Eder - * All Rights Reserved. - * - * This software is released under the terms of the GNU Lesser General Public License v2.1 - * A copy of which is available from http://www.gnu.org/copyleft/lesser.html - * - * We would be happy to list your phpagi based application on the phpagi - * website. Drop me an Email if you'd like us to list your program. - * - * @package phpAGI - * @version 2.0 - * @example docs/fastagi.xinetd Example xinetd config file - */ - - /** - * Written for PHP 4.3.4, should work with older PHP 4.x versions. - * Please submit bug reports, patches, etc to https://github.com/welltime/phpagi - * - */ - - require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); - - $fastagi = new AGI(); - - $fastagi->verbose(print_r($fastagi, true)); - - if(!isset($fastagi->config['fastagi']['basedir'])) - $fastagi->config['fastagi']['basedir'] = dirname(__FILE__); - - // perform some security checks - - $script = $fastagi->config['fastagi']['basedir'] . DIRECTORY_SEPARATOR . $fastagi->request['agi_network_script']; - - // in the same directory (or subdirectory) - $mydir = dirname($fastagi->config['fastagi']['basedir']) . DIRECTORY_SEPARATOR; - $dir = dirname($script) . DIRECTORY_SEPARATOR; - if(substr($dir, 0, strlen($mydir)) != $mydir) - { - $fastagi->conlog("$script is not allowed to execute."); - exit; - } - - // make sure it exists - if(!file_exists($script)) - { - $fastagi->conlog("$script does not exist."); - exit; - } - - // drop privileges - if(isset($fastagi->config['fastagi']['setuid']) && $fastagi->config['fastagi']['setuid']) - { - $owner = fileowner($script); - $group = filegroup($script); - if(!posix_setgid($group) || !posix_setegid($group) || !posix_setuid($owner) || !posix_seteuid($owner)) - { - $fastagi->conlog("failed to lower privileges."); - exit; - } - } - - // make sure script is still readable - if(!is_readable($script)) - { - $fastagi->conlog("$script is not readable."); - exit; - } - - require_once($script); -?> +#!/usr/local/bin/php -q +, David Eder + * All Rights Reserved. + * + * This software is released under the terms of the GNU Lesser General Public License v2.1 + * A copy of which is available from http://www.gnu.org/copyleft/lesser.html + * + * We would be happy to list your phpagi based application on the phpagi + * website. Drop me an Email if you'd like us to list your program. + * + * @package phpAGI + * @version 2.0 + * @example docs/fastagi.xinetd Example xinetd config file + */ + + /** + * Written for PHP 4.3.4, should work with older PHP 4.x versions. + * Please submit bug reports, patches, etc to https://github.com/ggets/phpagi + * + */ + + require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); + + $fastagi = new AGI(); + + $fastagi->verbose(print_r($fastagi, true)); + + if(!isset($fastagi->config['fastagi']['basedir'])) + $fastagi->config['fastagi']['basedir'] = dirname(__FILE__); + + // perform some security checks + + $script = $fastagi->config['fastagi']['basedir'] . DIRECTORY_SEPARATOR . $fastagi->request['agi_network_script']; + + // in the same directory (or subdirectory) + $mydir = dirname($fastagi->config['fastagi']['basedir']) . DIRECTORY_SEPARATOR; + $dir = dirname($script) . DIRECTORY_SEPARATOR; + if(substr($dir, 0, strlen($mydir)) != $mydir) + { + $fastagi->conlog("$script is not allowed to execute."); + exit; + } + + // make sure it exists + if(!file_exists($script)) + { + $fastagi->conlog("$script does not exist."); + exit; + } + + // drop privileges + if(isset($fastagi->config['fastagi']['setuid']) && $fastagi->config['fastagi']['setuid']) + { + $owner = fileowner($script); + $group = filegroup($script); + if(!posix_setgid($group) || !posix_setegid($group) || !posix_setuid($owner) || !posix_seteuid($owner)) + { + $fastagi->conlog("failed to lower privileges."); + exit; + } + } + + // make sure script is still readable + if(!is_readable($script)) + { + $fastagi->conlog("$script is not readable."); + exit; + } + + require_once($script); +?> diff --git a/src/phpagi.php b/src/phpagi.php index 6120897..fbd241f 100644 --- a/src/phpagi.php +++ b/src/phpagi.php @@ -1,1849 +1,1849 @@ -, David Eder and others - * All Rights Reserved. - * - * This software is released under the terms of the GNU Lesser General Public License v2.1 - * A copy of which is available from http://www.gnu.org/copyleft/lesser.html - * - * We would be happy to list your phpagi based application on the phpagi - * website. Drop me an Email if you'd like us to list your program. - * - * - * Written for PHP 4.3.4, should work with older PHP 4.x versions. - * - * Please submit bug reports, patches, etc to https://github.com/welltime/phpagi - * - * - * @package phpAGI - * @version 2.20 - */ - -if (!class_exists('AGI_AsteriskManager')) -{ - require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi-asmanager.php'); -} - -define('AST_CONFIG_DIR', '/etc/asterisk/'); -define('AST_SPOOL_DIR', '/var/spool/asterisk/'); -define('AST_TMP_DIR', AST_SPOOL_DIR . '/tmp/'); -define('DEFAULT_PHPAGI_CONFIG', AST_CONFIG_DIR . '/phpagi.conf'); - -define('AST_DIGIT_ANY', '0123456789#*'); - -define('AGIRES_OK', 200); - -define('AST_STATE_DOWN', 0); -define('AST_STATE_RESERVED', 1); -define('AST_STATE_OFFHOOK', 2); -define('AST_STATE_DIALING', 3); -define('AST_STATE_RING', 4); -define('AST_STATE_RINGING', 5); -define('AST_STATE_UP', 6); -define('AST_STATE_BUSY', 7); -define('AST_STATE_DIALING_OFFHOOK', 8); -define('AST_STATE_PRERING', 9); - -define('AUDIO_FILENO', 3); // STDERR_FILENO + 1 - -/** - * AGI class - * - * @package phpAGI - * @link http://www.voip-info.org/wiki-Asterisk+agi - * @example examples/dtmf.php Get DTMF tones from the user and say the digits - * @example examples/input.php Get text input from the user and say it back - * @example examples/ping.php Ping an IP address - */ -class AGI -{ - /** - * Request variables read in on initialization. - * - * Often contains any/all of the following: - * agi_request - name of agi script - * agi_channel - current channel - * agi_language - current language - * agi_type - channel type (SIP, ZAP, IAX, ...) - * agi_uniqueid - unique id based on unix time - * agi_callerid - callerID string - * agi_dnid - dialed number id - * agi_rdnis - referring DNIS number - * agi_context - current context - * agi_extension - extension dialed - * agi_priority - current priority - * agi_enhanced - value is 1.0 if started as an EAGI script - * agi_accountcode - set by SetAccount in the dialplan - * agi_network - value is yes if this is a fastagi - * agi_network_script - name of the script to execute - * - * NOTE: program arguments are still in $_SERVER['argv']. - * - * @var array - * @access public - */ - var $request; - - /** - * Config variables - * - * @var array - * @access public - */ - var $config; - - /** - * Asterisk Manager - * - * @var AGI_AsteriskManager - * @access public - */ - var $asmanager; - - /** - * Input Stream - * - * @access private - */ - var $in = null; - - /** - * Output Stream - * - * @access private - */ - var $out = null; - - /** - * Audio Stream - * - * @access public - */ - var $audio = null; - - - /** - * Application option delimiter - * - * @access public - */ - public $option_delim = ","; - - /** - * Constructor - * - * @param string $config is the name of the config file to parse - * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['phpagi'] - */ - function __construct($config=null, $optconfig=array()) - { - // load config - if(!is_null($config) && file_exists($config)) - $this->config = parse_ini_file($config, true); - elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) - $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); - - // If optconfig is specified, stuff vals and vars into 'phpagi' config array. - foreach($optconfig as $var=>$val) - $this->config['phpagi'][$var] = $val; - - // add default values to config for uninitialized values - if(!isset($this->config['phpagi']['error_handler'])) $this->config['phpagi']['error_handler'] = true; - if(!isset($this->config['phpagi']['debug'])) $this->config['phpagi']['debug'] = false; - if(!isset($this->config['phpagi']['admin'])) $this->config['phpagi']['admin'] = null; - if(!isset($this->config['phpagi']['tempdir'])) $this->config['phpagi']['tempdir'] = AST_TMP_DIR; - - // festival TTS config - if(!isset($this->config['festival']['text2wave'])) $this->config['festival']['text2wave'] = $this->which('text2wave'); - - // swift TTS config - if(!isset($this->config['cepstral']['swift'])) $this->config['cepstral']['swift'] = $this->which('swift'); - - ob_implicit_flush(true); - - // open stdin & stdout - $this->in = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); - $this->out = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); - - // initialize error handler - if($this->config['phpagi']['error_handler'] == true) - { - set_error_handler('phpagi_error_handler'); - global $phpagi_error_handler_email; - $phpagi_error_handler_email = $this->config['phpagi']['admin']; - error_reporting(E_ALL); - } - - // make sure temp folder exists - $this->make_folder($this->config['phpagi']['tempdir']); - - // read the request - $str = fgets($this->in); - while($str != "\n") - { - $this->request[substr($str, 0, strpos($str, ':'))] = trim(substr($str, strpos($str, ':') + 1)); - $str = fgets($this->in); - } - - // open audio if eagi detected - if($this->request['agi_enhanced'] == '1.0') - { - if(file_exists('/proc/' . getmypid() . '/fd/3')) - $this->audio = fopen('/proc/' . getmypid() . '/fd/3', 'r'); - elseif(file_exists('/dev/fd/3')) - { - // may need to mount fdescfs - $this->audio = fopen('/dev/fd/3', 'r'); - } - else - $this->conlog('Unable to open audio stream'); - - if($this->audio) stream_set_blocking($this->audio, 0); - } - - $this->conlog('AGI Request:'); - $this->conlog(print_r($this->request, true)); - $this->conlog('PHPAGI internal configuration:'); - $this->conlog(print_r($this->config, true)); - } - - // ********************************************************************************************************* - // ** COMMANDS ** - // ********************************************************************************************************* - - /** - * Answer channel if not already in answer state. - * - * @link http://www.voip-info.org/wiki-answer - * @example examples/dtmf.php Get DTMF tones from the user and say the digits - * @example examples/input.php Get text input from the user and say it back - * @example examples/ping.php Ping an IP address - * - * @return array, see evaluate for return information. ['result'] is 0 on success, -1 on failure. - */ - function answer() - { - return $this->evaluate('ANSWER'); - } - - /** - * Get the status of the specified channel. If no channel name is specified, return the status of the current channel. - * - * @link http://www.voip-info.org/wiki-channel+status - * @param string $channel - * @return array, see evaluate for return information. ['data'] contains description. - */ - function channel_status($channel='') - { - $ret = $this->evaluate("CHANNEL STATUS $channel"); - switch($ret['result']) - { - case -1: $ret['data'] = trim("There is no channel that matches $channel"); break; - case AST_STATE_DOWN: $ret['data'] = 'Channel is down and available'; break; - case AST_STATE_RESERVED: $ret['data'] = 'Channel is down, but reserved'; break; - case AST_STATE_OFFHOOK: $ret['data'] = 'Channel is off hook'; break; - case AST_STATE_DIALING: $ret['data'] = 'Digits (or equivalent) have been dialed'; break; - case AST_STATE_RING: $ret['data'] = 'Line is ringing'; break; - case AST_STATE_RINGING: $ret['data'] = 'Remote end is ringing'; break; - case AST_STATE_UP: $ret['data'] = 'Line is up'; break; - case AST_STATE_BUSY: $ret['data'] = 'Line is busy'; break; - case AST_STATE_DIALING_OFFHOOK: $ret['data'] = 'Digits (or equivalent) have been dialed while offhook'; break; - case AST_STATE_PRERING: $ret['data'] = 'Channel has detected an incoming call and is waiting for ring'; break; - default: $ret['data'] = "Unknown ({$ret['result']})"; break; - } - return $ret; - } - - /** - * Deletes an entry in the Asterisk database for a given family and key. - * - * @link http://www.voip-info.org/wiki-database+del - * @param string $family - * @param string $key - * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. - */ - function database_del($family, $key) - { - return $this->evaluate("DATABASE DEL \"$family\" \"$key\""); - } - - /** - * Deletes a family or specific keytree within a family in the Asterisk database. - * - * @link http://www.voip-info.org/wiki-database+deltree - * @param string $family - * @param string $keytree - * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. - */ - function database_deltree($family, $keytree='') - { - $cmd = "DATABASE DELTREE \"$family\""; - if($keytree != '') $cmd .= " \"$keytree\""; - return $this->evaluate($cmd); - } - - /** - * Retrieves an entry in the Asterisk database for a given family and key. - * - * @link http://www.voip-info.org/wiki-database+get - * @param string $family - * @param string $key - * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 failure. ['data'] holds the value - */ - function database_get($family, $key) - { - return $this->evaluate("DATABASE GET \"$family\" \"$key\""); - } - - /** - * Adds or updates an entry in the Asterisk database for a given family, key, and value. - * - * @param string $family - * @param string $key - * @param string $value - * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise - */ - function database_put($family, $key, $value) - { - $value = str_replace("\n", '\n', addslashes($value)); - return $this->evaluate("DATABASE PUT \"$family\" \"$key\" \"$value\""); - } - - - /** - * Sets a global variable, using Asterisk 1.6 syntax. - * - * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set - * - * @param string $pVariable - * @param string|int|float $pValue - * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise - */ - function set_global_var($pVariable, $pValue) - { - if (is_numeric($pValue)) - return $this->evaluate("Set({$pVariable}={$pValue},g);"); - else - return $this->evaluate("Set({$pVariable}=\"{$pValue}\",g);"); - } - - - /** - * Sets a variable, using Asterisk 1.6 syntax. - * - * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set - * - * @param string $pVariable - * @param string|int|float $pValue - * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise - */ - function set_var($pVariable, $pValue) - { - if (is_numeric($pValue)) - return $this->evaluate("Set({$pVariable}={$pValue});"); - else - return $this->evaluate("Set({$pVariable}=\"{$pValue}\");"); - } - - - /** - * Executes the specified Asterisk application with given options. - * - * @link http://www.voip-info.org/wiki-exec - * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands - * @param string $application - * @param mixed $options - * @return array, see evaluate for return information. ['result'] is whatever the application returns, or -2 on failure to find application - */ - function exec($application, $options) - { - if(is_array($options)) $options = join('|', $options); - return $this->evaluate("EXEC $application $options"); - } - - /** - * Plays the given file and receives DTMF data. - * - * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, - * while STREAM FILE returns immediately after the first DTMF digit is detected. - * - * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. - * - * If the user doesn't press any keys when the message plays, there is $timeout milliseconds - * of silence then the command ends. - * - * The user has the opportunity to press a key at any time during the message or the - * post-message silence. If the user presses a key while the message is playing, the - * message stops playing. When the first key is pressed a timer starts counting for - * $timeout milliseconds. Every time the user presses another key the timer is restarted. - * The command ends when the counter goes to zero or the maximum number of digits is entered, - * whichever happens first. - * - * If you don't specify a time out then a default timeout of 2000 is used following a pressed - * digit. If no digits are pressed then 6 seconds of silence follow the message. - * - * If you don't specify $max_digits then the user can enter as many digits as they want. - * - * Pressing the # key has the same effect as the timer running out: the command ends and - * any previously keyed digits are returned. A side effect of this is that there is no - * way to read a # key using this command. - * - * @example examples/ping.php Ping an IP address - * - * @link http://www.voip-info.org/wiki-get+data - * @param string $filename file to play. Do not include file extension. - * @param integer $timeout milliseconds - * @param integer $max_digits - * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. - * - * This differs from other commands with return DTMF as numbers representing ASCII characters. - */ - function get_data($filename, $timeout=null, $max_digits=null) - { - return $this->evaluate(rtrim("GET DATA $filename $timeout $max_digits")); - } - - /** - * Fetch the value of a variable. - * - * Does not work with global variables. Does not work with some variables that are generated by modules. - * - * @link http://www.voip-info.org/wiki-get+variable - * @link http://www.voip-info.org/wiki-Asterisk+variables - * @param string $variable name - * @param boolean $getvalue return the value only - * @return array, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value. returns value if $getvalue is TRUE - */ - function get_variable($variable,$getvalue=false) - { - $res=$this->evaluate("GET VARIABLE $variable"); - - if($getvalue==false) - return($res); - - return($res['data']); - } - - - /** - * Fetch the value of a full variable. - * - * - * @link http://www.voip-info.org/wiki/view/get+full+variable - * @link http://www.voip-info.org/wiki-Asterisk+variables - * @param string $variable name - * @param string $channel channel - * @param boolean $getvalue return the value only - * @return array, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value. returns value if $getvalue is TRUE - */ - function get_fullvariable($variable,$channel=false,$getvalue=false) - { - if($channel==false){ - $req = $variable; - } else { - $req = $variable.' '.$channel; - } - - $res=$this->evaluate('GET FULL VARIABLE '.$req); - - if($getvalue==false) - return($res); - - return($res['data']); - - } - - /** - * Hangup the specified channel. If no channel name is given, hang up the current channel. - * - * With power comes responsibility. Hanging up channels other than your own isn't something - * that is done routinely. If you are not sure why you are doing so, then don't. - * - * @link http://www.voip-info.org/wiki-hangup - * @example examples/dtmf.php Get DTMF tones from the user and say the digits - * @example examples/input.php Get text input from the user and say it back - * @example examples/ping.php Ping an IP address - * - * @param string $channel - * @return array, see evaluate for return information. ['result'] is 1 on success, -1 on failure. - */ - function hangup($channel='') - { - return $this->evaluate("HANGUP $channel"); - } - - /** - * Does nothing. - * - * @link http://www.voip-info.org/wiki-noop - * @return array, see evaluate for return information. - */ - function noop($string="") - { - return $this->evaluate("NOOP \"$string\""); - } - - /** - * Receive a character of text from a connected channel. Waits up to $timeout milliseconds for - * a character to arrive, or infinitely if $timeout is zero. - * - * @link http://www.voip-info.org/wiki-receive+char - * @param integer $timeout milliseconds - * @return array, see evaluate for return information. ['result'] is 0 on timeout or not supported, -1 on failure. Otherwise - * it is the decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function receive_char($timeout=-1) - { - return $this->evaluate("RECEIVE CHAR $timeout"); - } - - /** - * Record sound to a file until an acceptable DTMF digit is received or a specified amount of - * time has passed. Optionally the file BEEP is played before recording begins. - * - * @link http://www.voip-info.org/wiki-record+file - * @param string $file to record, without extension, often created in /var/lib/asterisk/sounds - * @param string $format of the file. GSM and WAV are commonly used formats. MP3 is read-only and thus cannot be used. - * @param string $escape_digits - * @param integer $timeout is the maximum record time in milliseconds, or -1 for no timeout. - * @param integer $offset to seek to without exceeding the end of the file. - * @param boolean $beep - * @param integer $silence number of seconds of silence allowed before the function returns despite the - * lack of dtmf digits or reaching timeout. - * @return array, see evaluate for return information. ['result'] is -1 on error, 0 on hangup, otherwise a decimal value of the - * DTMF tone. Use chr() to convert to ASCII. - */ - function record_file($file, $format, $escape_digits='', $timeout=-1, $offset=null, $beep=false, $silence=null) - { - $cmd = trim("RECORD FILE $file $format \"$escape_digits\" $timeout $offset"); - if($beep) $cmd .= ' BEEP'; - if(!is_null($silence)) $cmd .= " s=$silence"; - return $this->evaluate($cmd); - } - - /** - * Say a given character string, returning early if any of the given DTMF digits are received on the channel. - * - * @link https://www.voip-info.org/say-alpha - * @param string $text - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function say_alpha($text, $escape_digits='') - { - return $this->evaluate("SAY ALPHA $text \"$escape_digits\""); - } - /** - * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. - * - * @link http://www.voip-info.org/wiki-say+digits - * @param integer $digits - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function say_digits($digits, $escape_digits='') - { - return $this->evaluate("SAY DIGITS $digits \"$escape_digits\""); - } - - /** - * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. - * - * @link http://www.voip-info.org/wiki-say+number - * @param integer $number - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function say_number($number, $escape_digits='') - { - return $this->evaluate("SAY NUMBER $number \"$escape_digits\""); - } - - /** - * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. - * - * @link http://www.voip-info.org/wiki-say+phonetic - * @param string $text - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function say_phonetic($text, $escape_digits='') - { - return $this->evaluate("SAY PHONETIC $text \"$escape_digits\""); - } - - /** - * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. - * - * @link http://www.voip-info.org/wiki-say+time - * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function say_time($time=null, $escape_digits='') - { - if(is_null($time)) $time = time(); - return $this->evaluate("SAY TIME $time \"$escape_digits\""); - } - - /** - * Send the specified image on a channel. - * - * Most channels do not support the transmission of images. - * - * @link http://www.voip-info.org/wiki-send+image - * @param string $image without extension, often in /var/lib/asterisk/images - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the image is sent or - * channel does not support image transmission. - */ - function send_image($image) - { - return $this->evaluate("SEND IMAGE $image"); - } - - /** - * Send the given text to the connected channel. - * - * Most channels do not support transmission of text. - * - * @link http://www.voip-info.org/wiki-send+text - * @param $text - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the text is sent or - * channel does not support text transmission. - */ - function send_text($text) - { - return $this->evaluate("SEND TEXT \"$text\""); - } - - /** - * Cause the channel to automatically hangup at $time seconds in the future. - * If $time is 0 then the autohangup feature is disabled on this channel. - * - * If the channel is hungup prior to $time seconds, this setting has no effect. - * - * @link http://www.voip-info.org/wiki-set+autohangup - * @param integer $time until automatic hangup - * @return array, see evaluate for return information. - */ - function set_autohangup($time=0) - { - return $this->evaluate("SET AUTOHANGUP $time"); - } - - /** - * Changes the caller ID of the current channel. - * - * @link http://www.voip-info.org/wiki-set+callerid - * @param string $cid example: "John Smith"<1234567> - * This command will let you take liberties with the but the format shown in the example above works - * well: the name enclosed in double quotes followed immediately by the number inside angle brackets. If there is no name then - * you can omit it. If the name contains no spaces you can omit the double quotes around it. The number must follow the name - * immediately; don't put a space between them. The angle brackets around the number are necessary; if you omit them the - * number will be considered to be part of the name. - * @return array, see evaluate for return information. - */ - function set_callerid($cid) - { - return $this->evaluate("SET CALLERID $cid"); - } - - /** - * Sets the context for continuation upon exiting the application. - * - * Setting the context does NOT automatically reset the extension and the priority; if you want to start at the top of the new - * context you should set extension and priority yourself. - * - * If you specify a non-existent context you receive no error indication (['result'] is still 0) but you do get a - * warning message on the Asterisk console. - * - * @link http://www.voip-info.org/wiki-set+context - * @param string $context - * @return array, see evaluate for return information. - */ - function set_context($context) - { - return $this->evaluate("SET CONTEXT $context"); - } - - /** - * Set the extension to be used for continuation upon exiting the application. - * - * Setting the extension does NOT automatically reset the priority. If you want to start with the first priority of the - * extension you should set the priority yourself. - * - * If you specify a non-existent extension you receive no error indication (['result'] is still 0) but you do - * get a warning message on the Asterisk console. - * - * @link http://www.voip-info.org/wiki-set+extension - * @param string $extension - * @return array, see evaluate for return information. - */ - function set_extension($extension) - { - return $this->evaluate("SET EXTENSION $extension"); - } - - /** - * Enable/Disable Music on hold generator. - * - * @link http://www.voip-info.org/wiki-set+music - * @param boolean $enabled - * @param string $class - * @return array, see evaluate for return information. - */ - function set_music($enabled=true, $class='') - { - $enabled = ($enabled) ? 'ON' : 'OFF'; - return $this->evaluate("SET MUSIC $enabled $class"); - } - - /** - * Set the priority to be used for continuation upon exiting the application. - * - * If you specify a non-existent priority you receive no error indication (['result'] is still 0) - * and no warning is issued on the Asterisk console. - * - * @link http://www.voip-info.org/wiki-set+priority - * @param integer $priority - * @return array, see evaluate for return information. - */ - function set_priority($priority) - { - return $this->evaluate("SET PRIORITY $priority"); - } - - /** - * Sets a variable to the specified value. The variables so created can later be used by later using ${} - * in the dialplan. - * - * These variables live in the channel Asterisk creates when you pickup a phone and as such they are both local and temporary. - * Variables created in one channel can not be accessed by another channel. When you hang up the phone, the channel is deleted - * and any variables in that channel are deleted as well. - * - * @link http://www.voip-info.org/wiki-set+variable - * @param string $variable is case sensitive - * @param string $value - * @return array, see evaluate for return information. - */ - function set_variable($variable, $value) - { - $value = str_replace("\n", '\n', addslashes($value)); - return $this->evaluate("SET VARIABLE $variable \"$value\""); - } - - /** - * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA - * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of - * digits before returning. - * - * @example examples/ping.php Ping an IP address - * - * @link http://www.voip-info.org/wiki-stream+file - * @param string $filename without extension, often in /var/lib/asterisk/sounds - * @param string $escape_digits - * @param integer $offset - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function stream_file($filename, $escape_digits='', $offset=0) - { - return $this->evaluate("STREAM FILE $filename \"$escape_digits\" $offset"); - } - - /** - * Enable or disable TDD transmission/reception on the current channel. - * - * @link http://www.voip-info.org/wiki-tdd+mode - * @param string $setting can be on, off or mate - * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 if the channel is not TDD capable. - */ - function tdd_mode($setting) - { - return $this->evaluate("TDD MODE $setting"); - } - - /** - * Sends $message to the Asterisk console via the 'verbose' message system. - * - * If the Asterisk verbosity level is $level or greater, send $message to the console. - * - * The Asterisk verbosity system works as follows. The Asterisk user gets to set the desired verbosity at startup time or later - * using the console 'set verbose' command. Messages are displayed on the console if their verbose level is less than or equal - * to desired verbosity set by the user. More important messages should have a low verbose level; less important messages - * should have a high verbose level. - * - * @link http://www.voip-info.org/wiki-verbose - * @param string $message - * @param integer $level from 1 to 4 - * @return array, see evaluate for return information. - */ - function verbose($message, $level=1) - { - foreach(explode("\n", str_replace("\r\n", "\n", print_r($message, true))) as $msg) - { - @syslog(LOG_WARNING, $msg); - $ret = $this->evaluate("VERBOSE \"$msg\" $level"); - } - return $ret; - } - - /** - * Waits up to $timeout milliseconds for channel to receive a DTMF digit. - * - * @link http://www.voip-info.org/wiki-wait+for+digit - * @param integer $timeout in millisecons. Use -1 for the timeout value if you want the call to wait indefinitely. - * @return array, see evaluate for return information. ['result'] is 0 if wait completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function wait_for_digit($timeout=-1) - { - return $this->evaluate("WAIT FOR DIGIT $timeout"); - } - - - // ********************************************************************************************************* - // ** APPLICATIONS ** - // ********************************************************************************************************* - - /** - * Set absolute maximum time of call. - * - * Note that the timeout is set from the current time forward, not counting the number of seconds the call has already been up. - * Each time you call AbsoluteTimeout(), all previous absolute timeouts are cancelled. - * Will return the call to the T extension so that you can playback an explanatory note to the calling party (the called party - * will not hear that) - * - * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands - * @link http://www.dynx.net/ASTERISK/AGI/ccard/agi-ccard.agi - * @param $seconds allowed, 0 disables timeout - * @return array, see evaluate for return information. - */ - function exec_absolutetimeout($seconds=0) - { - return $this->exec('AbsoluteTimeout', $seconds); - } - - /** - * Executes an AGI compliant application. - * - * @param string $command - * @return array, see evaluate for return information. ['result'] is -1 on hangup or if application requested hangup, or 0 on non-hangup exit. - * @param string $args - */ - function exec_agi($command, $args) - { - return $this->exec("AGI $command", $args); - } - - /** - * Set Language. - * - * @param string $language code - * @return array, see evaluate for return information. - */ - function exec_setlanguage($language='en') - { - return $this->exec('Set', 'CHANNEL(language)='. $language); - } - - /** - * Do ENUM Lookup. - * - * Note: to retrieve the result, use - * get_variable('ENUM'); - * - * @param $exten - * @return array, see evaluate for return information. - */ - function exec_enumlookup($exten) - { - return $this->exec('EnumLookup', $exten); - } - - /** - * Dial. - * - * Dial takes input from ${VXML_URL} to send XML Url to Cisco 7960 - * Dial takes input from ${ALERT_INFO} to set ring cadence for Cisco phones - * Dial returns ${CAUSECODE}: If the dial failed, this is the errormessage. - * Dial returns ${DIALSTATUS}: Text code returning status of last dial attempt. - * - * @link http://www.voip-info.org/wiki-Asterisk+cmd+Dial - * @param string $type - * @param string $identifier - * @param integer $timeout - * @param string $options - * @param string $url - * @return array, see evaluate for return information. - */ - function exec_dial($type, $identifier, $timeout=null, $options=null, $url=null) - { - return $this->exec('Dial', trim("$type/$identifier".$this->option_delim.$timeout.$this->option_delim.$options.$this->option_delim.$url, $this->option_delim)); - } - - /** - * Goto. - * - * This function takes three arguments: context,extension, and priority, but the leading arguments - * are optional, not the trailing arguments. Thuse goto($z) sets the priority to $z. - * - * @param string $a - * @param string $b; - * @param string $c; - * @return array, see evaluate for return information. - */ - function exec_goto($a, $b=null, $c=null) - { - return $this->exec('Goto', trim($a.$this->option_delim.$b.$this->option_delim.$c, $this->option_delim)); - } - - - // ********************************************************************************************************* - // ** FAST PASSING ** - // ********************************************************************************************************* - - /** - * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. - * Return early if $buffer is adequate for request. - * - * @link http://www.voip-info.org/wiki-say+digits - * @param string $buffer - * @param integer $digits - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function fastpass_say_digits(&$buffer, $digits, $escape_digits='') - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->say_digits($digits, $escape_digits); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); - } - - /** - * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. - * Return early if $buffer is adequate for request. - * - * @link http://www.voip-info.org/wiki-say+number - * @param string $buffer - * @param integer $number - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function fastpass_say_number(&$buffer, $number, $escape_digits='') - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->say_number($number, $escape_digits); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); - } - - /** - * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. - * Return early if $buffer is adequate for request. - * - * @link http://www.voip-info.org/wiki-say+phonetic - * @param string $buffer - * @param string $text - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function fastpass_say_phonetic(&$buffer, $text, $escape_digits='') - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->say_phonetic($text, $escape_digits); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); - } - - /** - * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. - * Return early if $buffer is adequate for request. - * - * @link http://www.voip-info.org/wiki-say+time - * @param string $buffer - * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). - * @param string $escape_digits - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function fastpass_say_time(&$buffer, $time=null, $escape_digits='') - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->say_time($time, $escape_digits); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); - } - - /** - * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA - * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of - * digits before returning. - * Return early if $buffer is adequate for request. - * - * @link http://www.voip-info.org/wiki-stream+file - * @param string $buffer - * @param string $filename without extension, often in /var/lib/asterisk/sounds - * @param string $escape_digits - * @param integer $offset - * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no - * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. - */ - function fastpass_stream_file(&$buffer, $filename, $escape_digits='', $offset=0) - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->stream_file($filename, $escape_digits, $offset); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); - } - - /** - * Use festival to read text. - * Return early if $buffer is adequate for request. - * - * @link http://www.cstr.ed.ac.uk/projects/festival/ - * @param string $buffer - * @param string $text - * @param string $escape_digits - * @param integer $frequency - * @return array, see evaluate for return information. - */ - function fastpass_text2wav(&$buffer, $text, $escape_digits='', $frequency=8000) - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->text2wav($text, $escape_digits, $frequency); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); - } - - /** - * Use Cepstral Swift to read text. - * Return early if $buffer is adequate for request. - * - * @link http://www.cepstral.com/ - * @param string $buffer - * @param string $text - * @param string $escape_digits - * @param integer $frequency - * @return array, see evaluate for return information. - */ - function fastpass_swift(&$buffer, $text, $escape_digits='', $frequency=8000, $voice=null) - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->swift($text, $escape_digits, $frequency, $voice); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); - } - - /** - * Say Puncutation in a string. - * Return early if $buffer is adequate for request. - * - * @param string $buffer - * @param string $text - * @param string $escape_digits - * @param integer $frequency - * @return array, see evaluate for return information. - */ - function fastpass_say_punctuation(&$buffer, $text, $escape_digits='', $frequency=8000) - { - $proceed = false; - if($escape_digits != '' && $buffer != '') - { - if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) - $proceed = true; - } - if($buffer == '' || $proceed) - { - $res = $this->say_punctuation($text, $escape_digits, $frequency); - if($res['code'] == AGIRES_OK && $res['result'] > 0) - $buffer .= chr($res['result']); - return $res; - } - return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); - } - - /** - * Plays the given file and receives DTMF data. - * Return early if $buffer is adequate for request. - * - * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, - * while STREAM FILE returns immediately after the first DTMF digit is detected. - * - * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. - * - * If the user doesn't press any keys when the message plays, there is $timeout milliseconds - * of silence then the command ends. - * - * The user has the opportunity to press a key at any time during the message or the - * post-message silence. If the user presses a key while the message is playing, the - * message stops playing. When the first key is pressed a timer starts counting for - * $timeout milliseconds. Every time the user presses another key the timer is restarted. - * The command ends when the counter goes to zero or the maximum number of digits is entered, - * whichever happens first. - * - * If you don't specify a time out then a default timeout of 2000 is used following a pressed - * digit. If no digits are pressed then 6 seconds of silence follow the message. - * - * If you don't specify $max_digits then the user can enter as many digits as they want. - * - * Pressing the # key has the same effect as the timer running out: the command ends and - * any previously keyed digits are returned. A side effect of this is that there is no - * way to read a # key using this command. - * - * @link http://www.voip-info.org/wiki-get+data - * @param string $buffer - * @param string $filename file to play. Do not include file extension. - * @param integer $timeout milliseconds - * @param integer $max_digits - * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. - * - * This differs from other commands with return DTMF as numbers representing ASCII characters. - */ - function fastpass_get_data(&$buffer, $filename, $timeout=null, $max_digits=null) - { - if(is_null($max_digits) || strlen($buffer) < $max_digits) - { - if($buffer == '') - { - $res = $this->get_data($filename, $timeout, $max_digits); - if($res['code'] == AGIRES_OK) - $buffer .= $res['result']; - return $res; - } - else - { - while(is_null($max_digits) || strlen($buffer) < $max_digits) - { - $res = $this->wait_for_digit(); - if($res['code'] != AGIRES_OK) return $res; - if($res['result'] == ord('#')) break; - $buffer .= chr($res['result']); - } - } - } - return array('code'=>AGIRES_OK, 'result'=>$buffer); - } - - // ********************************************************************************************************* - // ** DERIVED ** - // ********************************************************************************************************* - - /** - * Menu. - * - * This function presents the user with a menu and reads the response - * - * @param array $choices has the following structure: - * array('1'=>'*Press 1 for this', // festival reads if prompt starts with * - * '2'=>'some-gsm-without-extension', - * '*'=>'*Press star for help'); - * @return mixed key pressed on sucess, -1 on failure - */ - function menu($choices, $timeout=2000) - { - $keys = join('', array_keys($choices)); - $choice = null; - while(is_null($choice)) - { - foreach($choices as $prompt) - { - if($prompt[0] == '*') - $ret = $this->text2wav(substr($prompt, 1), $keys); - else - $ret = $this->stream_file($prompt, $keys); - - if($ret['code'] != AGIRES_OK || $ret['result'] == -1) - { - $choice = -1; - break; - } - - if($ret['result'] != 0) - { - $choice = chr($ret['result']); - break; - } - } - - if(is_null($choice)) - { - $ret = $this->get_data('beep', $timeout, 1); - if($ret['code'] != AGIRES_OK || $ret['result'] == -1) - $choice = -1; - elseif($ret['result'] != '' && strpos(' '.$keys, $ret['result'])) - $choice = $ret['result']; - } - } - return $choice; - } - - /** - * setContext - Set context, extension and priority. - * - * @param string $context - * @param string $extension - * @param string $priority - */ - function setContext($context, $extension='s', $priority=1) - { - $this->set_context($context); - $this->set_extension($extension); - $this->set_priority($priority); - } - - /** - * Parse caller id. - * - * @example examples/dtmf.php Get DTMF tones from the user and say the digits - * @example examples/input.php Get text input from the user and say it back - * - * "name" - * - * @param string $callerid - * @return array('Name'=>$name, 'Number'=>$number) - */ - function parse_callerid($callerid=null) - { - if(is_null($callerid)) - $callerid = $this->request['agi_callerid']; - - $ret = array('name'=>'', 'protocol'=>'', 'username'=>'', 'host'=>'', 'port'=>''); - $callerid = trim($callerid); - - if($callerid[0] == '"' || $callerid[0] == "'") - { - $d = $callerid[0]; - $callerid = explode($d, substr($callerid, 1)); - $ret['name'] = array_shift($callerid); - $callerid = join($d, $callerid); - } - - $callerid = explode('@', trim($callerid, '<> ')); - $username = explode(':', array_shift($callerid)); - if(count($username) == 1) - $ret['username'] = $username[0]; - else - { - $ret['protocol'] = array_shift($username); - $ret['username'] = join(':', $username); - } - - $callerid = join('@', $callerid); - $host = explode(':', $callerid); - if(count($host) == 1) - $ret['host'] = $host[0]; - else - { - $ret['host'] = array_shift($host); - $ret['port'] = join(':', $host); - } - - return $ret; - } - - /** - * Use festival to read text. - * - * @example examples/dtmf.php Get DTMF tones from the user and say the digits - * @example examples/input.php Get text input from the user and say it back - * @example examples/ping.php Ping an IP address - * - * @link http://www.cstr.ed.ac.uk/projects/festival/ - * @param string $text - * @param string $escape_digits - * @param integer $frequency - * @return array, see evaluate for return information. - */ - function text2wav($text, $escape_digits='', $frequency=8000) - { - $text = trim($text); - if($text == '') return true; - - $hash = md5($text); - $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; - $fname .= 'text2wav_' . $hash; - - // create wave file - if(!file_exists("$fname.wav")) - { - // write text file - if(!file_exists("$fname.txt")) - { - $fp = fopen("$fname.txt", 'w'); - fputs($fp, $text); - fclose($fp); - } - - shell_exec("{$this->config['festival']['text2wave']} -F $frequency -o $fname.wav $fname.txt"); - } - else - { - touch("$fname.txt"); - touch("$fname.wav"); - } - - // stream it - $ret = $this->stream_file($fname, $escape_digits); - - // clean up old files - $delete = time() - 2592000; // 1 month - foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'text2wav_*') as $file) - if(filemtime($file) < $delete) - unlink($file); - - return $ret; - } - - /** - * Use Cepstral Swift to read text. - * - * @link http://www.cepstral.com/ - * @param string $text - * @param string $escape_digits - * @param integer $frequency - * @return array, see evaluate for return information. - */ - function swift($text, $escape_digits='', $frequency=8000, $voice=null) - { - if(!is_null($voice)) - $voice = "-n $voice"; - elseif(isset($this->config['cepstral']['voice'])) - $voice = "-n {$this->config['cepstral']['voice']}"; - - $text = trim($text); - if($text == '') return true; - - $hash = md5($text); - $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; - $fname .= 'swift_' . $hash; - - // create wave file - if(!file_exists("$fname.wav")) - { - // write text file - if(!file_exists("$fname.txt")) - { - $fp = fopen("$fname.txt", 'w'); - fputs($fp, $text); - fclose($fp); - } - - shell_exec("{$this->config['cepstral']['swift']} -p audio/channels=1,audio/sampling-rate=$frequency $voice -o $fname.wav -f $fname.txt"); - } - - // stream it - $ret = $this->stream_file($fname, $escape_digits); - - // clean up old files - $delete = time() - 2592000; // 1 month - foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'swift_*') as $file) - if(filemtime($file) < $delete) - unlink($file); - - return $ret; - } - - /** - * Text Input. - * - * Based on ideas found at http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText - * - * Example: - * UC H LC i , SP h o w SP a r e SP y o u ? - * $string = '*8'.'44*'.'*5'.'444*'.'00*'.'0*'.'44*'.'666*'.'9*'.'0*'.'2*'.'777*'.'33*'.'0*'.'999*'.'666*'.'88*'.'0000*'; - * - * @link http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText - * @example examples/input.php Get text input from the user and say it back - * - * @return string - */ - function text_input($mode='NUMERIC') - { - $alpha = array( 'k0'=>' ', 'k00'=>',', 'k000'=>'.', 'k0000'=>'?', 'k00000'=>'0', - 'k1'=>'!', 'k11'=>':', 'k111'=>';', 'k1111'=>'#', 'k11111'=>'1', - 'k2'=>'A', 'k22'=>'B', 'k222'=>'C', 'k2222'=>'2', - 'k3'=>'D', 'k33'=>'E', 'k333'=>'F', 'k3333'=>'3', - 'k4'=>'G', 'k44'=>'H', 'k444'=>'I', 'k4444'=>'4', - 'k5'=>'J', 'k55'=>'K', 'k555'=>'L', 'k5555'=>'5', - 'k6'=>'M', 'k66'=>'N', 'k666'=>'O', 'k6666'=>'6', - 'k7'=>'P', 'k77'=>'Q', 'k777'=>'R', 'k7777'=>'S', 'k77777'=>'7', - 'k8'=>'T', 'k88'=>'U', 'k888'=>'V', 'k8888'=>'8', - 'k9'=>'W', 'k99'=>'X', 'k999'=>'Y', 'k9999'=>'Z', 'k99999'=>'9'); - $symbol = array('k0'=>'=', - 'k1'=>'<', 'k11'=>'(', 'k111'=>'[', 'k1111'=>'{', 'k11111'=>'1', - 'k2'=>'@', 'k22'=>'$', 'k222'=>'&', 'k2222'=>'%', 'k22222'=>'2', - 'k3'=>'>', 'k33'=>')', 'k333'=>']', 'k3333'=>'}', 'k33333'=>'3', - 'k4'=>'+', 'k44'=>'-', 'k444'=>'*', 'k4444'=>'/', 'k44444'=>'4', - 'k5'=>"'", 'k55'=>'`', 'k555'=>'5', - 'k6'=>'"', 'k66'=>'6', - 'k7'=>'^', 'k77'=>'7', - 'k8'=>"\\",'k88'=>'|', 'k888'=>'8', - 'k9'=>'_', 'k99'=>'~', 'k999'=>'9'); - $text = ''; - do - { - $command = false; - $result = $this->get_data('beep'); - foreach(explode('*', $result['result']) as $code) - { - if($command) - { - switch($code[0]) - { - case '2': $text = substr($text, 0, strlen($text) - 1); break; // backspace - case '5': $mode = 'LOWERCASE'; break; - case '6': $mode = 'NUMERIC'; break; - case '7': $mode = 'SYMBOL'; break; - case '8': $mode = 'UPPERCASE'; break; - case '9': $text = explode(' ', $text); unset($text[count($text)-1]); $text = join(' ', $text); break; // backspace a word - } - $code = substr($code, 1); - $command = false; - } - if($code == '') - $command = true; - elseif($mode == 'NUMERIC') - $text .= $code; - elseif($mode == 'UPPERCASE' && isset($alpha['k'.$code])) - $text .= $alpha['k'.$code]; - elseif($mode == 'LOWERCASE' && isset($alpha['k'.$code])) - $text .= strtolower($alpha['k'.$code]); - elseif($mode == 'SYMBOL' && isset($symbol['k'.$code])) - $text .= $symbol['k'.$code]; - } - $this->say_punctuation($text); - } while(substr($result['result'], -2) == '**'); - return $text; - } - - /** - * Say Puncutation in a string. - * - * @param string $text - * @param string $escape_digits - * @param integer $frequency - * @return array, see evaluate for return information. - */ - function say_punctuation($text, $escape_digits='', $frequency=8000) - { - $ret=""; - for($i = 0; $i < strlen($text); $i++) - { - switch($text[$i]) - { - case ' ': $ret .= 'SPACE '; - case ',': $ret .= 'COMMA '; break; - case '.': $ret .= 'PERIOD '; break; - case '?': $ret .= 'QUESTION MARK '; break; - case '!': $ret .= 'EXPLANATION POINT '; break; - case ':': $ret .= 'COLON '; break; - case ';': $ret .= 'SEMICOLON '; break; - case '#': $ret .= 'POUND '; break; - case '=': $ret .= 'EQUALS '; break; - case '<': $ret .= 'LESS THAN '; break; - case '(': $ret .= 'LEFT PARENTHESIS '; break; - case '[': $ret .= 'LEFT BRACKET '; break; - case '{': $ret .= 'LEFT BRACE '; break; - case '@': $ret .= 'AT '; break; - case '$': $ret .= 'DOLLAR SIGN '; break; - case '&': $ret .= 'AMPERSAND '; break; - case '%': $ret .= 'PERCENT '; break; - case '>': $ret .= 'GREATER THAN '; break; - case ')': $ret .= 'RIGHT PARENTHESIS '; break; - case ']': $ret .= 'RIGHT BRACKET '; break; - case '}': $ret .= 'RIGHT BRACE '; break; - case '+': $ret .= 'PLUS '; break; - case '-': $ret .= 'MINUS '; break; - case '*': $ret .= 'ASTERISK '; break; - case '/': $ret .= 'SLASH '; break; - case "'": $ret .= 'SINGLE QUOTE '; break; - case '`': $ret .= 'BACK TICK '; break; - case '"': $ret .= 'QUOTE '; break; - case '^': $ret .= 'CAROT '; break; - case "\\": $ret .= 'BACK SLASH '; break; - case '|': $ret .= 'BAR '; break; - case '_': $ret .= 'UNDERSCORE '; break; - case '~': $ret .= 'TILDE '; break; - default: $ret .= $text[$i] . ' '; break; - } - } - return $this->text2wav($ret, $escape_digits, $frequency); - } - - /** - * Create a new AGI_AsteriskManager. - */ - function &new_AsteriskManager() - { - $this->asm = new AGI_AsteriskManager(null, $this->config['asmanager']); - $this->asm->setPagi($this); - $this->config['asmanager'] =& $this->asm->config['asmanager']; - return $this->asm; - } - - - // ********************************************************************************************************* - // ** PRIVATE ** - // ********************************************************************************************************* - - - /** - * Evaluate an AGI command. - * - * @access private - * @param string $command - * @return array ('code'=>$code, 'result'=>$result, 'data'=>$data) - */ - function evaluate($command) - { - $broken = array('code'=>500, 'result'=>-1, 'data'=>''); - - // write command - if(!@fwrite($this->out, trim($command) . "\n")) return $broken; - fflush($this->out); - - // Read result. Occasionally, a command return a string followed by an extra new line. - // When this happens, our script will ignore the new line, but it will still be in the - // buffer. So, if we get a blank line, it is probably the result of a previous - // command. We read until we get a valid result or asterisk hangs up. One offending - // command is SEND TEXT. - $count = 0; - do - { - $str = trim(fgets($this->in, 4096)); - } while($str == '' && $count++ < 5); - - if($count >= 5) - { - // $this->conlog("evaluate error on read for $command"); - return $broken; - } - - // parse result - $ret['code'] = substr($str, 0, 3); - $str = trim(substr($str, 3)); - - if($str[0] == '-') // we have a multiline response! - { - $count = 0; - $str = substr($str, 1) . "\n"; - $line = fgets($this->in, 4096); - while(substr($line, 0, 3) != $ret['code'] && $count < 5) - { - $str .= $line; - $line = fgets($this->in, 4096); - $count = (trim($line) == '') ? $count + 1 : 0; - } - if($count >= 5) - { - // $this->conlog("evaluate error on multiline read for $command"); - return $broken; - } - } - - $ret['result'] = null; - $ret['data'] = ''; - if($ret['code'] != AGIRES_OK) // some sort of error - { - $ret['data'] = $str; - $this->conlog(print_r($ret, true)); - } - else // normal AGIRES_OK response - { - $parse = explode(' ', trim($str)); - $in_token = false; - foreach($parse as $token) - { - if($in_token) // we previously hit a token starting with ')' but not ending in ')' - { - $ret['data'] .= ' ' . trim($token, '() '); - if($token[strlen($token)-1] == ')') $in_token = false; - } - elseif($token[0] == '(') - { - if($token[strlen($token)-1] != ')') $in_token = true; - $ret['data'] .= ' ' . trim($token, '() '); - } - elseif(strpos($token, '=')) - { - $token = explode('=', $token); - $ret[$token[0]] = $token[1]; - } - elseif($token != '') - $ret['data'] .= ' ' . $token; - } - $ret['data'] = trim($ret['data']); - } - - // log some errors - if($ret['result'] < 0) - $this->conlog("$command returned {$ret['result']}"); - - return $ret; - } - - /** - * Log to console if debug mode. - * - * @example examples/ping.php Ping an IP address - * - * @param string $str - * @param integer $vbl verbose level - */ - function conlog($str, $vbl=1) - { - static $busy = false; - - if($this->config['phpagi']['debug'] != false) - { - if(!$busy) // no conlogs inside conlog!!! - { - $busy = true; - $this->verbose($str, $vbl); - $busy = false; - } - } - } - - /** - * Find an execuable in the path. - * - * @access private - * @param string $cmd command to find - * @param string $checkpath path to check - * @return string the path to the command - */ - function which($cmd, $checkpath=null) - { - if (is_null($checkpath)) { - $chpath = getenv('PATH'); - if ($chpath === false) { - $chpath = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:'. - '/usr/X11R6/bin:/usr/local/apache/bin:/usr/local/mysql/bin'; - } - } else { - $chpath = $checkpath; - } - - foreach(explode(':', $chpath) as $path) - if(is_executable("$path/$cmd")) - return "$path/$cmd"; - - return false; - } - - /** - * Make a folder recursively. - * - * @access private - * @param string $folder - * @param integer $perms - * @return boolean - */ - function make_folder($folder, $perms=0755) - { - $f = explode(DIRECTORY_SEPARATOR, $folder); - $base = ''; - for($i = 0; $i < count($f); $i++) - { - $base .= $f[$i]; - if($f[$i] != '' && !file_exists($base)) { - if(mkdir($base, $perms)==false){ - return(false); - } - } - $base .= DIRECTORY_SEPARATOR; - } - return(true); - } - -} - - -/** - * error handler for phpagi. - * - * @param integer $level PHP error level - * @param string $message error message - * @param string $file path to file - * @param integer $line line number of error - * @param array $context variables in the current scope - */ -function phpagi_error_handler($level, $message, $file, $line, $context=null) -{ - if(ini_get('error_reporting') == 0) return; // this happens with an @ - - @syslog(LOG_WARNING, $file . '[' . $line . ']: ' . $message); - - global $phpagi_error_handler_email; - if(function_exists('mail') && !is_null($phpagi_error_handler_email)) // generate email debugging information - { - // decode error level - switch($level) - { - case E_WARNING: - case E_USER_WARNING: - $level = "Warning"; - break; - case E_NOTICE: - case E_USER_NOTICE: - $level = "Notice"; - break; - case E_USER_ERROR: - $level = "Error"; - break; - } - - // build message - $basefile = basename($file); - $subject = "$basefile/$line/$level: $message"; - $message = "$level: $message in $file on line $line\n\n"; - - if(strpos(' '.strtolower($message), 'mysql')) { - if(function_exists('mysql_errno')) { - $message .= 'MySQL error ' . mysql_errno() . ": " . mysql_error() . "\n\n"; - } - else if(function_exists('mysqli_errno')) { - $message .= 'MySQL error ' . mysqli_errno() . ": " . mysqli_error() . "\n\n"; - } - } - - // figure out who we are - if(function_exists('socket_create')) - { - $addr = null; - $port = 80; - $socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); - @socket_connect($socket, '64.0.0.0', $port); - @socket_getsockname($socket, $addr, $port); - @socket_close($socket); - $message .= "\n\nIP Address: $addr\n"; - } - - // include variables - $message .= "\n\nContext:\n" . print_r($context, true); - $message .= "\n\nGLOBALS:\n" . print_r($GLOBALS, true); - $message .= "\n\nBacktrace:\n" . print_r(debug_backtrace(), true); - - // include code fragment - if(file_exists($file)) - { - $message .= "\n\n$file:\n"; - $code = @file($file); - for($i = max(0, $line - 10); $i < min($line + 10, count($code)); $i++) - $message .= ($i + 1)."\t$code[$i]"; - } - - // make sure message is fully readable (convert unprintable chars to hex representation) - $ret = ''; - for($i = 0; $i < strlen($message); $i++) - { - $c = ord($message[$i]); - if($c == 10 || $c == 13 || $c == 9) - $ret .= $message[$i]; - elseif($c < 16) - $ret .= '\x0' . dechex($c); - elseif($c < 32 || $c > 127) - $ret .= '\x' . dechex($c); - else - $ret .= $message[$i]; - } - $message = $ret; - - // send the mail if less than 5 errors - static $mailcount = 0; - if($mailcount < 5) - @mail($phpagi_error_handler_email, $subject, $message); - $mailcount++; - } -} - -$phpagi_error_handler_email = null; - +, David Eder and others + * All Rights Reserved. + * + * This software is released under the terms of the GNU Lesser General Public License v2.1 + * A copy of which is available from http://www.gnu.org/copyleft/lesser.html + * + * We would be happy to list your phpagi based application on the phpagi + * website. Drop me an Email if you'd like us to list your program. + * + * + * Written for PHP 4.3.4, should work with older PHP 4.x versions. + * + * Please submit bug reports, patches, etc to https://github.com/ggets/phpagi + * + * + * @package phpAGI + * @version 2.20 + */ + +if (!class_exists('AGI_AsteriskManager')) +{ + require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi-asmanager.php'); +} + +define('AST_CONFIG_DIR', '/etc/asterisk/'); +define('AST_SPOOL_DIR', '/var/spool/asterisk/'); +define('AST_TMP_DIR', AST_SPOOL_DIR . '/tmp/'); +define('DEFAULT_PHPAGI_CONFIG', AST_CONFIG_DIR . '/phpagi.conf'); + +define('AST_DIGIT_ANY', '0123456789#*'); + +define('AGIRES_OK', 200); + +define('AST_STATE_DOWN', 0); +define('AST_STATE_RESERVED', 1); +define('AST_STATE_OFFHOOK', 2); +define('AST_STATE_DIALING', 3); +define('AST_STATE_RING', 4); +define('AST_STATE_RINGING', 5); +define('AST_STATE_UP', 6); +define('AST_STATE_BUSY', 7); +define('AST_STATE_DIALING_OFFHOOK', 8); +define('AST_STATE_PRERING', 9); + +define('AUDIO_FILENO', 3); // STDERR_FILENO + 1 + +/** + * AGI class + * + * @package phpAGI + * @link http://www.voip-info.org/wiki-Asterisk+agi + * @example examples/dtmf.php Get DTMF tones from the user and say the digits + * @example examples/input.php Get text input from the user and say it back + * @example examples/ping.php Ping an IP address + */ +class AGI +{ + /** + * Request variables read in on initialization. + * + * Often contains any/all of the following: + * agi_request - name of agi script + * agi_channel - current channel + * agi_language - current language + * agi_type - channel type (SIP, ZAP, IAX, ...) + * agi_uniqueid - unique id based on unix time + * agi_callerid - callerID string + * agi_dnid - dialed number id + * agi_rdnis - referring DNIS number + * agi_context - current context + * agi_extension - extension dialed + * agi_priority - current priority + * agi_enhanced - value is 1.0 if started as an EAGI script + * agi_accountcode - set by SetAccount in the dialplan + * agi_network - value is yes if this is a fastagi + * agi_network_script - name of the script to execute + * + * NOTE: program arguments are still in $_SERVER['argv']. + * + * @var array + * @access public + */ + var $request; + + /** + * Config variables + * + * @var array + * @access public + */ + var $config; + + /** + * Asterisk Manager + * + * @var AGI_AsteriskManager + * @access public + */ + var $asmanager; + + /** + * Input Stream + * + * @access private + */ + var $in = null; + + /** + * Output Stream + * + * @access private + */ + var $out = null; + + /** + * Audio Stream + * + * @access public + */ + var $audio = null; + + + /** + * Application option delimiter + * + * @access public + */ + public $option_delim = ","; + + /** + * Constructor + * + * @param string $config is the name of the config file to parse + * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['phpagi'] + */ + function __construct($config=null, $optconfig=array()) + { + // load config + if(!is_null($config) && file_exists($config)) + $this->config = parse_ini_file($config, true); + elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) + $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); + + // If optconfig is specified, stuff vals and vars into 'phpagi' config array. + foreach($optconfig as $var=>$val) + $this->config['phpagi'][$var] = $val; + + // add default values to config for uninitialized values + if(!isset($this->config['phpagi']['error_handler'])) $this->config['phpagi']['error_handler'] = true; + if(!isset($this->config['phpagi']['debug'])) $this->config['phpagi']['debug'] = false; + if(!isset($this->config['phpagi']['admin'])) $this->config['phpagi']['admin'] = null; + if(!isset($this->config['phpagi']['tempdir'])) $this->config['phpagi']['tempdir'] = AST_TMP_DIR; + + // festival TTS config + if(!isset($this->config['festival']['text2wave'])) $this->config['festival']['text2wave'] = $this->which('text2wave'); + + // swift TTS config + if(!isset($this->config['cepstral']['swift'])) $this->config['cepstral']['swift'] = $this->which('swift'); + + ob_implicit_flush(true); + + // open stdin & stdout + $this->in = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); + $this->out = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); + + // initialize error handler + if($this->config['phpagi']['error_handler'] == true) + { + set_error_handler('phpagi_error_handler'); + global $phpagi_error_handler_email; + $phpagi_error_handler_email = $this->config['phpagi']['admin']; + error_reporting(E_ALL); + } + + // make sure temp folder exists + $this->make_folder($this->config['phpagi']['tempdir']); + + // read the request + $str = fgets($this->in); + while($str != "\n") + { + $this->request[substr($str, 0, strpos($str, ':'))] = trim(substr($str, strpos($str, ':') + 1)); + $str = fgets($this->in); + } + + // open audio if eagi detected + if($this->request['agi_enhanced'] == '1.0') + { + if(file_exists('/proc/' . getmypid() . '/fd/3')) + $this->audio = fopen('/proc/' . getmypid() . '/fd/3', 'r'); + elseif(file_exists('/dev/fd/3')) + { + // may need to mount fdescfs + $this->audio = fopen('/dev/fd/3', 'r'); + } + else + $this->conlog('Unable to open audio stream'); + + if($this->audio) stream_set_blocking($this->audio, 0); + } + + $this->conlog('AGI Request:'); + $this->conlog(print_r($this->request, true)); + $this->conlog('PHPAGI internal configuration:'); + $this->conlog(print_r($this->config, true)); + } + + // ********************************************************************************************************* + // ** COMMANDS ** + // ********************************************************************************************************* + + /** + * Answer channel if not already in answer state. + * + * @link http://www.voip-info.org/wiki-answer + * @example examples/dtmf.php Get DTMF tones from the user and say the digits + * @example examples/input.php Get text input from the user and say it back + * @example examples/ping.php Ping an IP address + * + * @return array, see evaluate for return information. ['result'] is 0 on success, -1 on failure. + */ + function answer() + { + return $this->evaluate('ANSWER'); + } + + /** + * Get the status of the specified channel. If no channel name is specified, return the status of the current channel. + * + * @link http://www.voip-info.org/wiki-channel+status + * @param string $channel + * @return array, see evaluate for return information. ['data'] contains description. + */ + function channel_status($channel='') + { + $ret = $this->evaluate("CHANNEL STATUS $channel"); + switch($ret['result']) + { + case -1: $ret['data'] = trim("There is no channel that matches $channel"); break; + case AST_STATE_DOWN: $ret['data'] = 'Channel is down and available'; break; + case AST_STATE_RESERVED: $ret['data'] = 'Channel is down, but reserved'; break; + case AST_STATE_OFFHOOK: $ret['data'] = 'Channel is off hook'; break; + case AST_STATE_DIALING: $ret['data'] = 'Digits (or equivalent) have been dialed'; break; + case AST_STATE_RING: $ret['data'] = 'Line is ringing'; break; + case AST_STATE_RINGING: $ret['data'] = 'Remote end is ringing'; break; + case AST_STATE_UP: $ret['data'] = 'Line is up'; break; + case AST_STATE_BUSY: $ret['data'] = 'Line is busy'; break; + case AST_STATE_DIALING_OFFHOOK: $ret['data'] = 'Digits (or equivalent) have been dialed while offhook'; break; + case AST_STATE_PRERING: $ret['data'] = 'Channel has detected an incoming call and is waiting for ring'; break; + default: $ret['data'] = "Unknown ({$ret['result']})"; break; + } + return $ret; + } + + /** + * Deletes an entry in the Asterisk database for a given family and key. + * + * @link http://www.voip-info.org/wiki-database+del + * @param string $family + * @param string $key + * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. + */ + function database_del($family, $key) + { + return $this->evaluate("DATABASE DEL \"$family\" \"$key\""); + } + + /** + * Deletes a family or specific keytree within a family in the Asterisk database. + * + * @link http://www.voip-info.org/wiki-database+deltree + * @param string $family + * @param string $keytree + * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. + */ + function database_deltree($family, $keytree='') + { + $cmd = "DATABASE DELTREE \"$family\""; + if($keytree != '') $cmd .= " \"$keytree\""; + return $this->evaluate($cmd); + } + + /** + * Retrieves an entry in the Asterisk database for a given family and key. + * + * @link http://www.voip-info.org/wiki-database+get + * @param string $family + * @param string $key + * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 failure. ['data'] holds the value + */ + function database_get($family, $key) + { + return $this->evaluate("DATABASE GET \"$family\" \"$key\""); + } + + /** + * Adds or updates an entry in the Asterisk database for a given family, key, and value. + * + * @param string $family + * @param string $key + * @param string $value + * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise + */ + function database_put($family, $key, $value) + { + $value = str_replace("\n", '\n', addslashes($value)); + return $this->evaluate("DATABASE PUT \"$family\" \"$key\" \"$value\""); + } + + + /** + * Sets a global variable, using Asterisk 1.6 syntax. + * + * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set + * + * @param string $pVariable + * @param string|int|float $pValue + * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise + */ + function set_global_var($pVariable, $pValue) + { + if (is_numeric($pValue)) + return $this->evaluate("Set({$pVariable}={$pValue},g);"); + else + return $this->evaluate("Set({$pVariable}=\"{$pValue}\",g);"); + } + + + /** + * Sets a variable, using Asterisk 1.6 syntax. + * + * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set + * + * @param string $pVariable + * @param string|int|float $pValue + * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise + */ + function set_var($pVariable, $pValue) + { + if (is_numeric($pValue)) + return $this->evaluate("Set({$pVariable}={$pValue});"); + else + return $this->evaluate("Set({$pVariable}=\"{$pValue}\");"); + } + + + /** + * Executes the specified Asterisk application with given options. + * + * @link http://www.voip-info.org/wiki-exec + * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands + * @param string $application + * @param mixed $options + * @return array, see evaluate for return information. ['result'] is whatever the application returns, or -2 on failure to find application + */ + function exec($application, $options) + { + if(is_array($options)) $options = join('|', $options); + return $this->evaluate("EXEC $application $options"); + } + + /** + * Plays the given file and receives DTMF data. + * + * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, + * while STREAM FILE returns immediately after the first DTMF digit is detected. + * + * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. + * + * If the user doesn't press any keys when the message plays, there is $timeout milliseconds + * of silence then the command ends. + * + * The user has the opportunity to press a key at any time during the message or the + * post-message silence. If the user presses a key while the message is playing, the + * message stops playing. When the first key is pressed a timer starts counting for + * $timeout milliseconds. Every time the user presses another key the timer is restarted. + * The command ends when the counter goes to zero or the maximum number of digits is entered, + * whichever happens first. + * + * If you don't specify a time out then a default timeout of 2000 is used following a pressed + * digit. If no digits are pressed then 6 seconds of silence follow the message. + * + * If you don't specify $max_digits then the user can enter as many digits as they want. + * + * Pressing the # key has the same effect as the timer running out: the command ends and + * any previously keyed digits are returned. A side effect of this is that there is no + * way to read a # key using this command. + * + * @example examples/ping.php Ping an IP address + * + * @link http://www.voip-info.org/wiki-get+data + * @param string $filename file to play. Do not include file extension. + * @param integer $timeout milliseconds + * @param integer $max_digits + * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. + * + * This differs from other commands with return DTMF as numbers representing ASCII characters. + */ + function get_data($filename, $timeout=null, $max_digits=null) + { + return $this->evaluate(rtrim("GET DATA $filename $timeout $max_digits")); + } + + /** + * Fetch the value of a variable. + * + * Does not work with global variables. Does not work with some variables that are generated by modules. + * + * @link http://www.voip-info.org/wiki-get+variable + * @link http://www.voip-info.org/wiki-Asterisk+variables + * @param string $variable name + * @param boolean $getvalue return the value only + * @return array, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value. returns value if $getvalue is TRUE + */ + function get_variable($variable,$getvalue=false) + { + $res=$this->evaluate("GET VARIABLE $variable"); + + if($getvalue==false) + return($res); + + return($res['data']); + } + + + /** + * Fetch the value of a full variable. + * + * + * @link http://www.voip-info.org/wiki/view/get+full+variable + * @link http://www.voip-info.org/wiki-Asterisk+variables + * @param string $variable name + * @param string $channel channel + * @param boolean $getvalue return the value only + * @return array, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value. returns value if $getvalue is TRUE + */ + function get_fullvariable($variable,$channel=false,$getvalue=false) + { + if($channel==false){ + $req = $variable; + } else { + $req = $variable.' '.$channel; + } + + $res=$this->evaluate('GET FULL VARIABLE '.$req); + + if($getvalue==false) + return($res); + + return($res['data']); + + } + + /** + * Hangup the specified channel. If no channel name is given, hang up the current channel. + * + * With power comes responsibility. Hanging up channels other than your own isn't something + * that is done routinely. If you are not sure why you are doing so, then don't. + * + * @link http://www.voip-info.org/wiki-hangup + * @example examples/dtmf.php Get DTMF tones from the user and say the digits + * @example examples/input.php Get text input from the user and say it back + * @example examples/ping.php Ping an IP address + * + * @param string $channel + * @return array, see evaluate for return information. ['result'] is 1 on success, -1 on failure. + */ + function hangup($channel='') + { + return $this->evaluate("HANGUP $channel"); + } + + /** + * Does nothing. + * + * @link http://www.voip-info.org/wiki-noop + * @return array, see evaluate for return information. + */ + function noop($string="") + { + return $this->evaluate("NOOP \"$string\""); + } + + /** + * Receive a character of text from a connected channel. Waits up to $timeout milliseconds for + * a character to arrive, or infinitely if $timeout is zero. + * + * @link http://www.voip-info.org/wiki-receive+char + * @param integer $timeout milliseconds + * @return array, see evaluate for return information. ['result'] is 0 on timeout or not supported, -1 on failure. Otherwise + * it is the decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function receive_char($timeout=-1) + { + return $this->evaluate("RECEIVE CHAR $timeout"); + } + + /** + * Record sound to a file until an acceptable DTMF digit is received or a specified amount of + * time has passed. Optionally the file BEEP is played before recording begins. + * + * @link http://www.voip-info.org/wiki-record+file + * @param string $file to record, without extension, often created in /var/lib/asterisk/sounds + * @param string $format of the file. GSM and WAV are commonly used formats. MP3 is read-only and thus cannot be used. + * @param string $escape_digits + * @param integer $timeout is the maximum record time in milliseconds, or -1 for no timeout. + * @param integer $offset to seek to without exceeding the end of the file. + * @param boolean $beep + * @param integer $silence number of seconds of silence allowed before the function returns despite the + * lack of dtmf digits or reaching timeout. + * @return array, see evaluate for return information. ['result'] is -1 on error, 0 on hangup, otherwise a decimal value of the + * DTMF tone. Use chr() to convert to ASCII. + */ + function record_file($file, $format, $escape_digits='', $timeout=-1, $offset=null, $beep=false, $silence=null) + { + $cmd = trim("RECORD FILE $file $format \"$escape_digits\" $timeout $offset"); + if($beep) $cmd .= ' BEEP'; + if(!is_null($silence)) $cmd .= " s=$silence"; + return $this->evaluate($cmd); + } + + /** + * Say a given character string, returning early if any of the given DTMF digits are received on the channel. + * + * @link https://www.voip-info.org/say-alpha + * @param string $text + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function say_alpha($text, $escape_digits='') + { + return $this->evaluate("SAY ALPHA $text \"$escape_digits\""); + } + /** + * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. + * + * @link http://www.voip-info.org/wiki-say+digits + * @param integer $digits + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function say_digits($digits, $escape_digits='') + { + return $this->evaluate("SAY DIGITS $digits \"$escape_digits\""); + } + + /** + * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. + * + * @link http://www.voip-info.org/wiki-say+number + * @param integer $number + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function say_number($number, $escape_digits='') + { + return $this->evaluate("SAY NUMBER $number \"$escape_digits\""); + } + + /** + * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. + * + * @link http://www.voip-info.org/wiki-say+phonetic + * @param string $text + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function say_phonetic($text, $escape_digits='') + { + return $this->evaluate("SAY PHONETIC $text \"$escape_digits\""); + } + + /** + * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. + * + * @link http://www.voip-info.org/wiki-say+time + * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function say_time($time=null, $escape_digits='') + { + if(is_null($time)) $time = time(); + return $this->evaluate("SAY TIME $time \"$escape_digits\""); + } + + /** + * Send the specified image on a channel. + * + * Most channels do not support the transmission of images. + * + * @link http://www.voip-info.org/wiki-send+image + * @param string $image without extension, often in /var/lib/asterisk/images + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the image is sent or + * channel does not support image transmission. + */ + function send_image($image) + { + return $this->evaluate("SEND IMAGE $image"); + } + + /** + * Send the given text to the connected channel. + * + * Most channels do not support transmission of text. + * + * @link http://www.voip-info.org/wiki-send+text + * @param $text + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the text is sent or + * channel does not support text transmission. + */ + function send_text($text) + { + return $this->evaluate("SEND TEXT \"$text\""); + } + + /** + * Cause the channel to automatically hangup at $time seconds in the future. + * If $time is 0 then the autohangup feature is disabled on this channel. + * + * If the channel is hungup prior to $time seconds, this setting has no effect. + * + * @link http://www.voip-info.org/wiki-set+autohangup + * @param integer $time until automatic hangup + * @return array, see evaluate for return information. + */ + function set_autohangup($time=0) + { + return $this->evaluate("SET AUTOHANGUP $time"); + } + + /** + * Changes the caller ID of the current channel. + * + * @link http://www.voip-info.org/wiki-set+callerid + * @param string $cid example: "John Smith"<1234567> + * This command will let you take liberties with the but the format shown in the example above works + * well: the name enclosed in double quotes followed immediately by the number inside angle brackets. If there is no name then + * you can omit it. If the name contains no spaces you can omit the double quotes around it. The number must follow the name + * immediately; don't put a space between them. The angle brackets around the number are necessary; if you omit them the + * number will be considered to be part of the name. + * @return array, see evaluate for return information. + */ + function set_callerid($cid) + { + return $this->evaluate("SET CALLERID $cid"); + } + + /** + * Sets the context for continuation upon exiting the application. + * + * Setting the context does NOT automatically reset the extension and the priority; if you want to start at the top of the new + * context you should set extension and priority yourself. + * + * If you specify a non-existent context you receive no error indication (['result'] is still 0) but you do get a + * warning message on the Asterisk console. + * + * @link http://www.voip-info.org/wiki-set+context + * @param string $context + * @return array, see evaluate for return information. + */ + function set_context($context) + { + return $this->evaluate("SET CONTEXT $context"); + } + + /** + * Set the extension to be used for continuation upon exiting the application. + * + * Setting the extension does NOT automatically reset the priority. If you want to start with the first priority of the + * extension you should set the priority yourself. + * + * If you specify a non-existent extension you receive no error indication (['result'] is still 0) but you do + * get a warning message on the Asterisk console. + * + * @link http://www.voip-info.org/wiki-set+extension + * @param string $extension + * @return array, see evaluate for return information. + */ + function set_extension($extension) + { + return $this->evaluate("SET EXTENSION $extension"); + } + + /** + * Enable/Disable Music on hold generator. + * + * @link http://www.voip-info.org/wiki-set+music + * @param boolean $enabled + * @param string $class + * @return array, see evaluate for return information. + */ + function set_music($enabled=true, $class='') + { + $enabled = ($enabled) ? 'ON' : 'OFF'; + return $this->evaluate("SET MUSIC $enabled $class"); + } + + /** + * Set the priority to be used for continuation upon exiting the application. + * + * If you specify a non-existent priority you receive no error indication (['result'] is still 0) + * and no warning is issued on the Asterisk console. + * + * @link http://www.voip-info.org/wiki-set+priority + * @param integer $priority + * @return array, see evaluate for return information. + */ + function set_priority($priority) + { + return $this->evaluate("SET PRIORITY $priority"); + } + + /** + * Sets a variable to the specified value. The variables so created can later be used by later using ${} + * in the dialplan. + * + * These variables live in the channel Asterisk creates when you pickup a phone and as such they are both local and temporary. + * Variables created in one channel can not be accessed by another channel. When you hang up the phone, the channel is deleted + * and any variables in that channel are deleted as well. + * + * @link http://www.voip-info.org/wiki-set+variable + * @param string $variable is case sensitive + * @param string $value + * @return array, see evaluate for return information. + */ + function set_variable($variable, $value) + { + $value = str_replace("\n", '\n', addslashes($value)); + return $this->evaluate("SET VARIABLE $variable \"$value\""); + } + + /** + * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA + * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of + * digits before returning. + * + * @example examples/ping.php Ping an IP address + * + * @link http://www.voip-info.org/wiki-stream+file + * @param string $filename without extension, often in /var/lib/asterisk/sounds + * @param string $escape_digits + * @param integer $offset + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function stream_file($filename, $escape_digits='', $offset=0) + { + return $this->evaluate("STREAM FILE $filename \"$escape_digits\" $offset"); + } + + /** + * Enable or disable TDD transmission/reception on the current channel. + * + * @link http://www.voip-info.org/wiki-tdd+mode + * @param string $setting can be on, off or mate + * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 if the channel is not TDD capable. + */ + function tdd_mode($setting) + { + return $this->evaluate("TDD MODE $setting"); + } + + /** + * Sends $message to the Asterisk console via the 'verbose' message system. + * + * If the Asterisk verbosity level is $level or greater, send $message to the console. + * + * The Asterisk verbosity system works as follows. The Asterisk user gets to set the desired verbosity at startup time or later + * using the console 'set verbose' command. Messages are displayed on the console if their verbose level is less than or equal + * to desired verbosity set by the user. More important messages should have a low verbose level; less important messages + * should have a high verbose level. + * + * @link http://www.voip-info.org/wiki-verbose + * @param string $message + * @param integer $level from 1 to 4 + * @return array, see evaluate for return information. + */ + function verbose($message, $level=1) + { + foreach(explode("\n", str_replace("\r\n", "\n", print_r($message, true))) as $msg) + { + @syslog(LOG_WARNING, $msg); + $ret = $this->evaluate("VERBOSE \"$msg\" $level"); + } + return $ret; + } + + /** + * Waits up to $timeout milliseconds for channel to receive a DTMF digit. + * + * @link http://www.voip-info.org/wiki-wait+for+digit + * @param integer $timeout in millisecons. Use -1 for the timeout value if you want the call to wait indefinitely. + * @return array, see evaluate for return information. ['result'] is 0 if wait completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function wait_for_digit($timeout=-1) + { + return $this->evaluate("WAIT FOR DIGIT $timeout"); + } + + + // ********************************************************************************************************* + // ** APPLICATIONS ** + // ********************************************************************************************************* + + /** + * Set absolute maximum time of call. + * + * Note that the timeout is set from the current time forward, not counting the number of seconds the call has already been up. + * Each time you call AbsoluteTimeout(), all previous absolute timeouts are cancelled. + * Will return the call to the T extension so that you can playback an explanatory note to the calling party (the called party + * will not hear that) + * + * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands + * @link http://www.dynx.net/ASTERISK/AGI/ccard/agi-ccard.agi + * @param $seconds allowed, 0 disables timeout + * @return array, see evaluate for return information. + */ + function exec_absolutetimeout($seconds=0) + { + return $this->exec('AbsoluteTimeout', $seconds); + } + + /** + * Executes an AGI compliant application. + * + * @param string $command + * @return array, see evaluate for return information. ['result'] is -1 on hangup or if application requested hangup, or 0 on non-hangup exit. + * @param string $args + */ + function exec_agi($command, $args) + { + return $this->exec("AGI $command", $args); + } + + /** + * Set Language. + * + * @param string $language code + * @return array, see evaluate for return information. + */ + function exec_setlanguage($language='en') + { + return $this->exec('Set', 'CHANNEL(language)='. $language); + } + + /** + * Do ENUM Lookup. + * + * Note: to retrieve the result, use + * get_variable('ENUM'); + * + * @param $exten + * @return array, see evaluate for return information. + */ + function exec_enumlookup($exten) + { + return $this->exec('EnumLookup', $exten); + } + + /** + * Dial. + * + * Dial takes input from ${VXML_URL} to send XML Url to Cisco 7960 + * Dial takes input from ${ALERT_INFO} to set ring cadence for Cisco phones + * Dial returns ${CAUSECODE}: If the dial failed, this is the errormessage. + * Dial returns ${DIALSTATUS}: Text code returning status of last dial attempt. + * + * @link http://www.voip-info.org/wiki-Asterisk+cmd+Dial + * @param string $type + * @param string $identifier + * @param integer $timeout + * @param string $options + * @param string $url + * @return array, see evaluate for return information. + */ + function exec_dial($type, $identifier, $timeout=null, $options=null, $url=null) + { + return $this->exec('Dial', trim("$type/$identifier".$this->option_delim.$timeout.$this->option_delim.$options.$this->option_delim.$url, $this->option_delim)); + } + + /** + * Goto. + * + * This function takes three arguments: context,extension, and priority, but the leading arguments + * are optional, not the trailing arguments. Thuse goto($z) sets the priority to $z. + * + * @param string $a + * @param string $b; + * @param string $c; + * @return array, see evaluate for return information. + */ + function exec_goto($a, $b=null, $c=null) + { + return $this->exec('Goto', trim($a.$this->option_delim.$b.$this->option_delim.$c, $this->option_delim)); + } + + + // ********************************************************************************************************* + // ** FAST PASSING ** + // ********************************************************************************************************* + + /** + * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. + * Return early if $buffer is adequate for request. + * + * @link http://www.voip-info.org/wiki-say+digits + * @param string $buffer + * @param integer $digits + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function fastpass_say_digits(&$buffer, $digits, $escape_digits='') + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->say_digits($digits, $escape_digits); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); + } + + /** + * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. + * Return early if $buffer is adequate for request. + * + * @link http://www.voip-info.org/wiki-say+number + * @param string $buffer + * @param integer $number + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function fastpass_say_number(&$buffer, $number, $escape_digits='') + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->say_number($number, $escape_digits); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); + } + + /** + * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. + * Return early if $buffer is adequate for request. + * + * @link http://www.voip-info.org/wiki-say+phonetic + * @param string $buffer + * @param string $text + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function fastpass_say_phonetic(&$buffer, $text, $escape_digits='') + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->say_phonetic($text, $escape_digits); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); + } + + /** + * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. + * Return early if $buffer is adequate for request. + * + * @link http://www.voip-info.org/wiki-say+time + * @param string $buffer + * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). + * @param string $escape_digits + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function fastpass_say_time(&$buffer, $time=null, $escape_digits='') + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->say_time($time, $escape_digits); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); + } + + /** + * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA + * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of + * digits before returning. + * Return early if $buffer is adequate for request. + * + * @link http://www.voip-info.org/wiki-stream+file + * @param string $buffer + * @param string $filename without extension, often in /var/lib/asterisk/sounds + * @param string $escape_digits + * @param integer $offset + * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no + * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. + */ + function fastpass_stream_file(&$buffer, $filename, $escape_digits='', $offset=0) + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->stream_file($filename, $escape_digits, $offset); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); + } + + /** + * Use festival to read text. + * Return early if $buffer is adequate for request. + * + * @link http://www.cstr.ed.ac.uk/projects/festival/ + * @param string $buffer + * @param string $text + * @param string $escape_digits + * @param integer $frequency + * @return array, see evaluate for return information. + */ + function fastpass_text2wav(&$buffer, $text, $escape_digits='', $frequency=8000) + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->text2wav($text, $escape_digits, $frequency); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); + } + + /** + * Use Cepstral Swift to read text. + * Return early if $buffer is adequate for request. + * + * @link http://www.cepstral.com/ + * @param string $buffer + * @param string $text + * @param string $escape_digits + * @param integer $frequency + * @return array, see evaluate for return information. + */ + function fastpass_swift(&$buffer, $text, $escape_digits='', $frequency=8000, $voice=null) + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->swift($text, $escape_digits, $frequency, $voice); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); + } + + /** + * Say Puncutation in a string. + * Return early if $buffer is adequate for request. + * + * @param string $buffer + * @param string $text + * @param string $escape_digits + * @param integer $frequency + * @return array, see evaluate for return information. + */ + function fastpass_say_punctuation(&$buffer, $text, $escape_digits='', $frequency=8000) + { + $proceed = false; + if($escape_digits != '' && $buffer != '') + { + if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) + $proceed = true; + } + if($buffer == '' || $proceed) + { + $res = $this->say_punctuation($text, $escape_digits, $frequency); + if($res['code'] == AGIRES_OK && $res['result'] > 0) + $buffer .= chr($res['result']); + return $res; + } + return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); + } + + /** + * Plays the given file and receives DTMF data. + * Return early if $buffer is adequate for request. + * + * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, + * while STREAM FILE returns immediately after the first DTMF digit is detected. + * + * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. + * + * If the user doesn't press any keys when the message plays, there is $timeout milliseconds + * of silence then the command ends. + * + * The user has the opportunity to press a key at any time during the message or the + * post-message silence. If the user presses a key while the message is playing, the + * message stops playing. When the first key is pressed a timer starts counting for + * $timeout milliseconds. Every time the user presses another key the timer is restarted. + * The command ends when the counter goes to zero or the maximum number of digits is entered, + * whichever happens first. + * + * If you don't specify a time out then a default timeout of 2000 is used following a pressed + * digit. If no digits are pressed then 6 seconds of silence follow the message. + * + * If you don't specify $max_digits then the user can enter as many digits as they want. + * + * Pressing the # key has the same effect as the timer running out: the command ends and + * any previously keyed digits are returned. A side effect of this is that there is no + * way to read a # key using this command. + * + * @link http://www.voip-info.org/wiki-get+data + * @param string $buffer + * @param string $filename file to play. Do not include file extension. + * @param integer $timeout milliseconds + * @param integer $max_digits + * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. + * + * This differs from other commands with return DTMF as numbers representing ASCII characters. + */ + function fastpass_get_data(&$buffer, $filename, $timeout=null, $max_digits=null) + { + if(is_null($max_digits) || strlen($buffer) < $max_digits) + { + if($buffer == '') + { + $res = $this->get_data($filename, $timeout, $max_digits); + if($res['code'] == AGIRES_OK) + $buffer .= $res['result']; + return $res; + } + else + { + while(is_null($max_digits) || strlen($buffer) < $max_digits) + { + $res = $this->wait_for_digit(); + if($res['code'] != AGIRES_OK) return $res; + if($res['result'] == ord('#')) break; + $buffer .= chr($res['result']); + } + } + } + return array('code'=>AGIRES_OK, 'result'=>$buffer); + } + + // ********************************************************************************************************* + // ** DERIVED ** + // ********************************************************************************************************* + + /** + * Menu. + * + * This function presents the user with a menu and reads the response + * + * @param array $choices has the following structure: + * array('1'=>'*Press 1 for this', // festival reads if prompt starts with * + * '2'=>'some-gsm-without-extension', + * '*'=>'*Press star for help'); + * @return mixed key pressed on sucess, -1 on failure + */ + function menu($choices, $timeout=2000) + { + $keys = join('', array_keys($choices)); + $choice = null; + while(is_null($choice)) + { + foreach($choices as $prompt) + { + if($prompt[0] == '*') + $ret = $this->text2wav(substr($prompt, 1), $keys); + else + $ret = $this->stream_file($prompt, $keys); + + if($ret['code'] != AGIRES_OK || $ret['result'] == -1) + { + $choice = -1; + break; + } + + if($ret['result'] != 0) + { + $choice = chr($ret['result']); + break; + } + } + + if(is_null($choice)) + { + $ret = $this->get_data('beep', $timeout, 1); + if($ret['code'] != AGIRES_OK || $ret['result'] == -1) + $choice = -1; + elseif($ret['result'] != '' && strpos(' '.$keys, $ret['result'])) + $choice = $ret['result']; + } + } + return $choice; + } + + /** + * setContext - Set context, extension and priority. + * + * @param string $context + * @param string $extension + * @param string $priority + */ + function setContext($context, $extension='s', $priority=1) + { + $this->set_context($context); + $this->set_extension($extension); + $this->set_priority($priority); + } + + /** + * Parse caller id. + * + * @example examples/dtmf.php Get DTMF tones from the user and say the digits + * @example examples/input.php Get text input from the user and say it back + * + * "name" + * + * @param string $callerid + * @return array('Name'=>$name, 'Number'=>$number) + */ + function parse_callerid($callerid=null) + { + if(is_null($callerid)) + $callerid = $this->request['agi_callerid']; + + $ret = array('name'=>'', 'protocol'=>'', 'username'=>'', 'host'=>'', 'port'=>''); + $callerid = trim($callerid); + + if($callerid[0] == '"' || $callerid[0] == "'") + { + $d = $callerid[0]; + $callerid = explode($d, substr($callerid, 1)); + $ret['name'] = array_shift($callerid); + $callerid = join($d, $callerid); + } + + $callerid = explode('@', trim($callerid, '<> ')); + $username = explode(':', array_shift($callerid)); + if(count($username) == 1) + $ret['username'] = $username[0]; + else + { + $ret['protocol'] = array_shift($username); + $ret['username'] = join(':', $username); + } + + $callerid = join('@', $callerid); + $host = explode(':', $callerid); + if(count($host) == 1) + $ret['host'] = $host[0]; + else + { + $ret['host'] = array_shift($host); + $ret['port'] = join(':', $host); + } + + return $ret; + } + + /** + * Use festival to read text. + * + * @example examples/dtmf.php Get DTMF tones from the user and say the digits + * @example examples/input.php Get text input from the user and say it back + * @example examples/ping.php Ping an IP address + * + * @link http://www.cstr.ed.ac.uk/projects/festival/ + * @param string $text + * @param string $escape_digits + * @param integer $frequency + * @return array, see evaluate for return information. + */ + function text2wav($text, $escape_digits='', $frequency=8000) + { + $text = trim($text); + if($text == '') return true; + + $hash = md5($text); + $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; + $fname .= 'text2wav_' . $hash; + + // create wave file + if(!file_exists("$fname.wav")) + { + // write text file + if(!file_exists("$fname.txt")) + { + $fp = fopen("$fname.txt", 'w'); + fputs($fp, $text); + fclose($fp); + } + + shell_exec("{$this->config['festival']['text2wave']} -F $frequency -o $fname.wav $fname.txt"); + } + else + { + touch("$fname.txt"); + touch("$fname.wav"); + } + + // stream it + $ret = $this->stream_file($fname, $escape_digits); + + // clean up old files + $delete = time() - 2592000; // 1 month + foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'text2wav_*') as $file) + if(filemtime($file) < $delete) + unlink($file); + + return $ret; + } + + /** + * Use Cepstral Swift to read text. + * + * @link http://www.cepstral.com/ + * @param string $text + * @param string $escape_digits + * @param integer $frequency + * @return array, see evaluate for return information. + */ + function swift($text, $escape_digits='', $frequency=8000, $voice=null) + { + if(!is_null($voice)) + $voice = "-n $voice"; + elseif(isset($this->config['cepstral']['voice'])) + $voice = "-n {$this->config['cepstral']['voice']}"; + + $text = trim($text); + if($text == '') return true; + + $hash = md5($text); + $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; + $fname .= 'swift_' . $hash; + + // create wave file + if(!file_exists("$fname.wav")) + { + // write text file + if(!file_exists("$fname.txt")) + { + $fp = fopen("$fname.txt", 'w'); + fputs($fp, $text); + fclose($fp); + } + + shell_exec("{$this->config['cepstral']['swift']} -p audio/channels=1,audio/sampling-rate=$frequency $voice -o $fname.wav -f $fname.txt"); + } + + // stream it + $ret = $this->stream_file($fname, $escape_digits); + + // clean up old files + $delete = time() - 2592000; // 1 month + foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'swift_*') as $file) + if(filemtime($file) < $delete) + unlink($file); + + return $ret; + } + + /** + * Text Input. + * + * Based on ideas found at http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText + * + * Example: + * UC H LC i , SP h o w SP a r e SP y o u ? + * $string = '*8'.'44*'.'*5'.'444*'.'00*'.'0*'.'44*'.'666*'.'9*'.'0*'.'2*'.'777*'.'33*'.'0*'.'999*'.'666*'.'88*'.'0000*'; + * + * @link http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText + * @example examples/input.php Get text input from the user and say it back + * + * @return string + */ + function text_input($mode='NUMERIC') + { + $alpha = array( 'k0'=>' ', 'k00'=>',', 'k000'=>'.', 'k0000'=>'?', 'k00000'=>'0', + 'k1'=>'!', 'k11'=>':', 'k111'=>';', 'k1111'=>'#', 'k11111'=>'1', + 'k2'=>'A', 'k22'=>'B', 'k222'=>'C', 'k2222'=>'2', + 'k3'=>'D', 'k33'=>'E', 'k333'=>'F', 'k3333'=>'3', + 'k4'=>'G', 'k44'=>'H', 'k444'=>'I', 'k4444'=>'4', + 'k5'=>'J', 'k55'=>'K', 'k555'=>'L', 'k5555'=>'5', + 'k6'=>'M', 'k66'=>'N', 'k666'=>'O', 'k6666'=>'6', + 'k7'=>'P', 'k77'=>'Q', 'k777'=>'R', 'k7777'=>'S', 'k77777'=>'7', + 'k8'=>'T', 'k88'=>'U', 'k888'=>'V', 'k8888'=>'8', + 'k9'=>'W', 'k99'=>'X', 'k999'=>'Y', 'k9999'=>'Z', 'k99999'=>'9'); + $symbol = array('k0'=>'=', + 'k1'=>'<', 'k11'=>'(', 'k111'=>'[', 'k1111'=>'{', 'k11111'=>'1', + 'k2'=>'@', 'k22'=>'$', 'k222'=>'&', 'k2222'=>'%', 'k22222'=>'2', + 'k3'=>'>', 'k33'=>')', 'k333'=>']', 'k3333'=>'}', 'k33333'=>'3', + 'k4'=>'+', 'k44'=>'-', 'k444'=>'*', 'k4444'=>'/', 'k44444'=>'4', + 'k5'=>"'", 'k55'=>'`', 'k555'=>'5', + 'k6'=>'"', 'k66'=>'6', + 'k7'=>'^', 'k77'=>'7', + 'k8'=>"\\",'k88'=>'|', 'k888'=>'8', + 'k9'=>'_', 'k99'=>'~', 'k999'=>'9'); + $text = ''; + do + { + $command = false; + $result = $this->get_data('beep'); + foreach(explode('*', $result['result']) as $code) + { + if($command) + { + switch($code[0]) + { + case '2': $text = substr($text, 0, strlen($text) - 1); break; // backspace + case '5': $mode = 'LOWERCASE'; break; + case '6': $mode = 'NUMERIC'; break; + case '7': $mode = 'SYMBOL'; break; + case '8': $mode = 'UPPERCASE'; break; + case '9': $text = explode(' ', $text); unset($text[count($text)-1]); $text = join(' ', $text); break; // backspace a word + } + $code = substr($code, 1); + $command = false; + } + if($code == '') + $command = true; + elseif($mode == 'NUMERIC') + $text .= $code; + elseif($mode == 'UPPERCASE' && isset($alpha['k'.$code])) + $text .= $alpha['k'.$code]; + elseif($mode == 'LOWERCASE' && isset($alpha['k'.$code])) + $text .= strtolower($alpha['k'.$code]); + elseif($mode == 'SYMBOL' && isset($symbol['k'.$code])) + $text .= $symbol['k'.$code]; + } + $this->say_punctuation($text); + } while(substr($result['result'], -2) == '**'); + return $text; + } + + /** + * Say Puncutation in a string. + * + * @param string $text + * @param string $escape_digits + * @param integer $frequency + * @return array, see evaluate for return information. + */ + function say_punctuation($text, $escape_digits='', $frequency=8000) + { + $ret=""; + for($i = 0; $i < strlen($text); $i++) + { + switch($text[$i]) + { + case ' ': $ret .= 'SPACE '; + case ',': $ret .= 'COMMA '; break; + case '.': $ret .= 'PERIOD '; break; + case '?': $ret .= 'QUESTION MARK '; break; + case '!': $ret .= 'EXPLANATION POINT '; break; + case ':': $ret .= 'COLON '; break; + case ';': $ret .= 'SEMICOLON '; break; + case '#': $ret .= 'POUND '; break; + case '=': $ret .= 'EQUALS '; break; + case '<': $ret .= 'LESS THAN '; break; + case '(': $ret .= 'LEFT PARENTHESIS '; break; + case '[': $ret .= 'LEFT BRACKET '; break; + case '{': $ret .= 'LEFT BRACE '; break; + case '@': $ret .= 'AT '; break; + case '$': $ret .= 'DOLLAR SIGN '; break; + case '&': $ret .= 'AMPERSAND '; break; + case '%': $ret .= 'PERCENT '; break; + case '>': $ret .= 'GREATER THAN '; break; + case ')': $ret .= 'RIGHT PARENTHESIS '; break; + case ']': $ret .= 'RIGHT BRACKET '; break; + case '}': $ret .= 'RIGHT BRACE '; break; + case '+': $ret .= 'PLUS '; break; + case '-': $ret .= 'MINUS '; break; + case '*': $ret .= 'ASTERISK '; break; + case '/': $ret .= 'SLASH '; break; + case "'": $ret .= 'SINGLE QUOTE '; break; + case '`': $ret .= 'BACK TICK '; break; + case '"': $ret .= 'QUOTE '; break; + case '^': $ret .= 'CAROT '; break; + case "\\": $ret .= 'BACK SLASH '; break; + case '|': $ret .= 'BAR '; break; + case '_': $ret .= 'UNDERSCORE '; break; + case '~': $ret .= 'TILDE '; break; + default: $ret .= $text[$i] . ' '; break; + } + } + return $this->text2wav($ret, $escape_digits, $frequency); + } + + /** + * Create a new AGI_AsteriskManager. + */ + function &new_AsteriskManager() + { + $this->asm = new AGI_AsteriskManager(null, $this->config['asmanager']); + $this->asm->setPagi($this); + $this->config['asmanager'] =& $this->asm->config['asmanager']; + return $this->asm; + } + + + // ********************************************************************************************************* + // ** PRIVATE ** + // ********************************************************************************************************* + + + /** + * Evaluate an AGI command. + * + * @access private + * @param string $command + * @return array ('code'=>$code, 'result'=>$result, 'data'=>$data) + */ + function evaluate($command) + { + $broken = array('code'=>500, 'result'=>-1, 'data'=>''); + + // write command + if(!@fwrite($this->out, trim($command) . "\n")) return $broken; + fflush($this->out); + + // Read result. Occasionally, a command return a string followed by an extra new line. + // When this happens, our script will ignore the new line, but it will still be in the + // buffer. So, if we get a blank line, it is probably the result of a previous + // command. We read until we get a valid result or asterisk hangs up. One offending + // command is SEND TEXT. + $count = 0; + do + { + $str = trim(fgets($this->in, 4096)); + } while($str == '' && $count++ < 5); + + if($count >= 5) + { + // $this->conlog("evaluate error on read for $command"); + return $broken; + } + + // parse result + $ret['code'] = substr($str, 0, 3); + $str = trim(substr($str, 3)); + + if($str[0] == '-') // we have a multiline response! + { + $count = 0; + $str = substr($str, 1) . "\n"; + $line = fgets($this->in, 4096); + while(substr($line, 0, 3) != $ret['code'] && $count < 5) + { + $str .= $line; + $line = fgets($this->in, 4096); + $count = (trim($line) == '') ? $count + 1 : 0; + } + if($count >= 5) + { + // $this->conlog("evaluate error on multiline read for $command"); + return $broken; + } + } + + $ret['result'] = null; + $ret['data'] = ''; + if($ret['code'] != AGIRES_OK) // some sort of error + { + $ret['data'] = $str; + $this->conlog(print_r($ret, true)); + } + else // normal AGIRES_OK response + { + $parse = explode(' ', trim($str)); + $in_token = false; + foreach($parse as $token) + { + if($in_token) // we previously hit a token starting with ')' but not ending in ')' + { + $ret['data'] .= ' ' . trim($token, '() '); + if($token[strlen($token)-1] == ')') $in_token = false; + } + elseif($token[0] == '(') + { + if($token[strlen($token)-1] != ')') $in_token = true; + $ret['data'] .= ' ' . trim($token, '() '); + } + elseif(strpos($token, '=')) + { + $token = explode('=', $token); + $ret[$token[0]] = $token[1]; + } + elseif($token != '') + $ret['data'] .= ' ' . $token; + } + $ret['data'] = trim($ret['data']); + } + + // log some errors + if($ret['result'] < 0) + $this->conlog("$command returned {$ret['result']}"); + + return $ret; + } + + /** + * Log to console if debug mode. + * + * @example examples/ping.php Ping an IP address + * + * @param string $str + * @param integer $vbl verbose level + */ + function conlog($str, $vbl=1) + { + static $busy = false; + + if($this->config['phpagi']['debug'] != false) + { + if(!$busy) // no conlogs inside conlog!!! + { + $busy = true; + $this->verbose($str, $vbl); + $busy = false; + } + } + } + + /** + * Find an execuable in the path. + * + * @access private + * @param string $cmd command to find + * @param string $checkpath path to check + * @return string the path to the command + */ + function which($cmd, $checkpath=null) + { + if (is_null($checkpath)) { + $chpath = getenv('PATH'); + if ($chpath === false) { + $chpath = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:'. + '/usr/X11R6/bin:/usr/local/apache/bin:/usr/local/mysql/bin'; + } + } else { + $chpath = $checkpath; + } + + foreach(explode(':', $chpath) as $path) + if(is_executable("$path/$cmd")) + return "$path/$cmd"; + + return false; + } + + /** + * Make a folder recursively. + * + * @access private + * @param string $folder + * @param integer $perms + * @return boolean + */ + function make_folder($folder, $perms=0755) + { + $f = explode(DIRECTORY_SEPARATOR, $folder); + $base = ''; + for($i = 0; $i < count($f); $i++) + { + $base .= $f[$i]; + if($f[$i] != '' && !file_exists($base)) { + if(mkdir($base, $perms)==false){ + return(false); + } + } + $base .= DIRECTORY_SEPARATOR; + } + return(true); + } + +} + + +/** + * error handler for phpagi. + * + * @param integer $level PHP error level + * @param string $message error message + * @param string $file path to file + * @param integer $line line number of error + * @param array $context variables in the current scope + */ +function phpagi_error_handler($level, $message, $file, $line, $context=null) +{ + if(ini_get('error_reporting') == 0) return; // this happens with an @ + + @syslog(LOG_WARNING, $file . '[' . $line . ']: ' . $message); + + global $phpagi_error_handler_email; + if(function_exists('mail') && !is_null($phpagi_error_handler_email)) // generate email debugging information + { + // decode error level + switch($level) + { + case E_WARNING: + case E_USER_WARNING: + $level = "Warning"; + break; + case E_NOTICE: + case E_USER_NOTICE: + $level = "Notice"; + break; + case E_USER_ERROR: + $level = "Error"; + break; + } + + // build message + $basefile = basename($file); + $subject = "$basefile/$line/$level: $message"; + $message = "$level: $message in $file on line $line\n\n"; + + if(strpos(' '.strtolower($message), 'mysql')) { + if(function_exists('mysql_errno')) { + $message .= 'MySQL error ' . mysql_errno() . ": " . mysql_error() . "\n\n"; + } + else if(function_exists('mysqli_errno')) { + $message .= 'MySQL error ' . mysqli_errno() . ": " . mysqli_error() . "\n\n"; + } + } + + // figure out who we are + if(function_exists('socket_create')) + { + $addr = null; + $port = 80; + $socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + @socket_connect($socket, '64.0.0.0', $port); + @socket_getsockname($socket, $addr, $port); + @socket_close($socket); + $message .= "\n\nIP Address: $addr\n"; + } + + // include variables + $message .= "\n\nContext:\n" . print_r($context, true); + $message .= "\n\nGLOBALS:\n" . print_r($GLOBALS, true); + $message .= "\n\nBacktrace:\n" . print_r(debug_backtrace(), true); + + // include code fragment + if(file_exists($file)) + { + $message .= "\n\n$file:\n"; + $code = @file($file); + for($i = max(0, $line - 10); $i < min($line + 10, count($code)); $i++) + $message .= ($i + 1)."\t$code[$i]"; + } + + // make sure message is fully readable (convert unprintable chars to hex representation) + $ret = ''; + for($i = 0; $i < strlen($message); $i++) + { + $c = ord($message[$i]); + if($c == 10 || $c == 13 || $c == 9) + $ret .= $message[$i]; + elseif($c < 16) + $ret .= '\x0' . dechex($c); + elseif($c < 32 || $c > 127) + $ret .= '\x' . dechex($c); + else + $ret .= $message[$i]; + } + $message = $ret; + + // send the mail if less than 5 errors + static $mailcount = 0; + if($mailcount < 5) + @mail($phpagi_error_handler_email, $subject, $message); + $mailcount++; + } +} + +$phpagi_error_handler_email = null; +