├── .gitignore ├── COPYING ├── README.md ├── composer.json ├── docs ├── CHANGELOG ├── README.phpagi ├── README.phpagi-asmanager ├── README.phpagi-fastagi ├── fastagi.xinetd └── phpagi.example.conf ├── mkdocs.php └── src ├── phpagi-asmanager.php ├── phpagi-fastagi.php └── phpagi.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /composer.lock 3 | /composer.phar 4 | /vendor/ 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHPAGI README 2 | ------------- 3 | 4 | Welcome to PHPAGI. 5 | 6 | phpagi is a set of PHP classes for use in developing applications with 7 | the Asterisk Gateway Interface, and is licensed under the GNU Lesser 8 | General Public License (see COPYING for terms). 9 | 10 | This release (version 2) of the phpagi classes is a significant overhaul 11 | from the old version 1 library. API functions have been renamed and 12 | restructured. 13 | 14 | Version 1 of phpagi is no longer supported, but will continue to be 15 | available for historical purposes. We strongly encourage you to migrate 16 | to this new version. 17 | 18 | If you have developed software based around phpagi, we'd like to hear from 19 | you! Drop us a note, and indicate whether you'd like us to list your 20 | application on our website. 21 | 22 | ## Installation 23 | 24 | The preferred way to install this extension is through [composer](https://getcomposer.org/download/). 25 | 26 | Either run 27 | 28 | ```bash 29 | $ composer require welltime/phpagi ^2.20 30 | ``` 31 | 32 | or add 33 | 34 | ``` 35 | "welltime/phpagi": "^2.20" 36 | ``` 37 | 38 | to the ```require``` section of your `composer.json` file. 39 | 40 | FILES 41 | ----- 42 | * phpagi.php - The main phpagi class. 43 | * phpagi-asmanager.php - The Asterisk Manager class. 44 | * phpagi-fastagi.php - FastAGI class. 45 | 46 | * docs/ - README files for the classes. 47 | * api-docs/ - API Documentation (html) 48 | 49 | DOCS 50 | ---- 51 | * README.phpagi - The main phpagi README 52 | * README.phpagi-asmanager - The phpagi asterisk manager README 53 | * README.phpagi-fastagi - phpagi fastagi README 54 | * CHANGELOG - Change Log. 55 | 56 | * phpagi.conf - An example configuration file for phpagi. 57 | * fastagi.xinetd - xinetd.conf sample configuration for fastagi 58 | 59 | SUPPORT 60 | ------- 61 | 62 | Support for phpagi is available from the project website. 63 | 64 | * https://github.com/welltime/phpagi 65 | 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "welltime/phpagi", 3 | "description": "PHPAGI is a PHP class for the Asterisk Gateway Interface.", 4 | "type": "library", 5 | "license": "LGPL-2.1-or-later", 6 | "autoload": { 7 | "classmap": [ 8 | "src/phpagi.php", 9 | "src/phpagi-asmanager.php" 10 | ] 11 | }, 12 | "require": {} 13 | } 14 | -------------------------------------------------------------------------------- /docs/CHANGELOG: -------------------------------------------------------------------------------- 1 | ---------- 2 | CHANGES 3 | ---------- 4 | Sep 18, 2010 - Matthew Asham 5 | * Add option_delim to AGI to specify application option delimiter 6 | * Changed constructor method to __construct 7 | 8 | Sep 17, 2010 - Matthew Asham 9 | * BACKWARDS COMPATABILITY WARNING: renamed goto() method to setContext() to address PHP 5.3 reserved keyword. tracker #3031708 10 | * add get_fullvariable to AGI class 11 | * add boolean flag to get_variable and get_fullvariable methods to 12 | return only the value - tracker #1736629 13 | * initialize $ret variable in say_punctuation 14 | * add string parameter to noop 15 | * fix escape issue in database_get 16 | * make_folder now returns true or false depending on success 17 | * update exec_setlanguage to use asterisk 1.6+ language 18 | 19 | May 19, 2010 - Roland Hu 20 | * new methods: set_var(), set_global_var() 21 | * dialplan bug (using pipe instead of comma) is fixed in exec_dial() and exec_goto() 22 | * code formatting 23 | 24 | June 8, 2010 - Matthew Asham 25 | * move to svn from cvs 26 | 27 | May 25, 2005 - David Eder 28 | * Added fastpass support. 29 | 30 | May 18, 2005 - David Eder 31 | * Added phpagi_1.php as a wrapper class for 1.12 compatibility. 32 | 33 | March 25, 2005 - David Eder 34 | * Changed the way text2wave and swift are executed for better compatibility. 35 | * Added caching to text2wav and swift. 36 | 37 | March 12, 2005 - David Eder 38 | * fixed autohangup in phpagi-asmanager.php, renamed to set_autohangup. 39 | * Added more documentation to phpagi-asmanager.php. 40 | * Added weather example. 41 | 42 | March 4, 2005 - David Eder 43 | * FastAGI via xinetd 44 | 45 | February 17, 2005 - David Eder 46 | * Fixed bugs with error handler. 47 | 48 | February 16, 2005 - David Eder 49 | * Added Cepstral swift TTS patch from C. Arbusti of Soluzioni Vocali ( http://soluzionivocali.it ). 50 | * Extended swift TTS functionality. 51 | * Moved tempdir from [festival] to [phpagi] so that it can be shared with other extensions. 52 | 53 | February 2, 2005 - David Eder 54 | * More documentation. 55 | * Reworked and reintegrated AGI_AsteriskManager class. 56 | 57 | January 21, 2005 - David Eder 58 | * Added exec_dial($type, $identifier, $timeout=NULL, $options=NULL, $url=NULL). 59 | * Added exec_goto($a, $b=NULL, $c=NULL). 60 | * Fixed bugs in evaluate, better support of multiline and closed input and output. 61 | * Fixed bugs in config initialization. 62 | 63 | January 19, 2005 - David Eder 64 | * SUMMARY: 65 | * Massive restructuring! 66 | * Updated to use more PHP internal functions. 67 | * Updated function arguments to represent their AGI function's arguments. 68 | * Functions now return a consistent result array. 69 | * Added phpdoc documentation. 70 | * Removed functions that can be done with internal PHP or AGI functions. 71 | * Enhanced error handler. 72 | * 73 | * Goals of changes: 74 | * 75 | * It is important for a language API to not wander too far from the general 76 | * API. Functions that are named differently have been updated. Function 77 | * arguments that do not match the AGI API have been updated. Underscores 78 | * have been substituted for spaces to make them compatible with PHP function 79 | * names. Optional arguments in AGI should remain optional if possible. Return 80 | * values should be consistent. Functions need to be better documented. 81 | * 82 | * Result: 83 | * 84 | * The return from most functions is now 85 | * array('code'=>$code, 'result'=>$result, 'data'=>$data) 86 | * ['data'] still needs some work. 87 | * 88 | * removed class variables: 89 | * $response - It was no longer used with the new return structure. 90 | * 91 | * removed functions: 92 | * agi_is_error($retarr) - It was no longer used with the new return 93 | * structure. Each function has it's own result that must be evalutated 94 | * by the programmer, as the return values are often specific to the function. 95 | * agi_readresult($str=FALSE) - It was no longer used with the new return structure. 96 | * agi_response_code() - It was no longer used with the new return structure. 97 | * agi_response_result() - It was no longer used with the new return structure. 98 | * agi_response_data() - It was no longer used with the new return structure. 99 | * agi_response_var($var) - It was no longer used with the new return structure. 100 | * agi_response_is_error() - see agi_is_error 101 | * agi_read() - It was deprecated 102 | * con_print_r($arr,$label='',$lvl=0) - Use print_r($arr, true) in conjunction with conlog 103 | * agi_getdtmf($len,$timeout,$terminator=FALSE,$prompt=FALSE) - use get_data 104 | * agi_dtmf2text($len,$timeout,$terminator=FALSE,$prompt=FALSE) - use text_input 105 | * arr2str($arr) - use PHP function join 106 | * config_load($file) - use PHP function parse_ini_file 107 | * enum_lookup($telnumber,$rDNS="e164.org") - use exec_enumlookup 108 | * enum_txtlookup($telnumber,$rDNS="e164.org") - use exec_enumlookup 109 | * 110 | * added functions: 111 | * answer() 112 | * autohangup($time=0) 113 | * exec($application, $options) 114 | * get_data($filename, $timeout=NULL, $max_digits=NULL) 115 | * receive_char($timeout=-1) 116 | * say_phonetic($text, $escape_digits='') 117 | * set_context($context) 118 | * set_extension($extension) 119 | * set_priority($priority) 120 | * tdd_mode($setting) 121 | * wait_for_digit($timeout=-1) 122 | * database_deltree($family, $keytree='') 123 | * noop() 124 | * set_music($enabled=true, $class='') 125 | * exec_absolutetimeout($seconds=0) 126 | * exec_agi($command, $args) 127 | * exec_enumlookup($exten) 128 | * text_input($mode='NUMERIC') 129 | * say_punctuation($text, $escape_digits='', $frequency=8000) 130 | * which($cmd, $checkpath=NULL) 131 | * make_folder($folder, $perms=0755) 132 | * updated functions: 133 | * agi_exec($str) -> evaluate($command) - exec is an AGI function 134 | * agi_verbose($str,$vbl=1) -> verbose($message, $level=1) - consistency with AGI 135 | * db_get($family,$key) -> database_get($family, $key) - consistency with AGI 136 | * db_put($family,$key,$val) -> database_put($family, $key, $value) - consistency with AGI 137 | * db_del($family,$key) -> database_del($family, $key) - consistency with AGI 138 | * get_var($var) -> get_variable($variable) - consistency with AGI 139 | * set_var($var,$val) -> set_variable($variable, $value) - consistency with AGI 140 | * agi_hangup() -> hangup($channel='') - consistency with AGI 141 | * agi_channel_status($channel) -> channel_status($channel='') - consistency with AGI 142 | * agi_recordfile($file,$format,$timeout=5000,$prompt=FALSE) -> record_file($file, $format, $escape_digits='', $timeout=-1, $beep=false, $silence=NULL) - consistency with AGI 143 | * agi_play($file) -> stream_file($filename, $escape_digits='', $offset=0) - consistency with AGI 144 | * agi_goto($con,$ext='s',$pri=1) -> goto($context, $extension='s', $priority=1) - consistency with AGI 145 | * agi_saydigits($digits) -> say_digits($digits, $escape_digits='') - consistency with AGI 146 | * agi_saynumber($number) -> say_number($number, $escape_digits='') - consistency with AGI 147 | * agi_saytime($time="") -> say_time($time=NULL, $escape_digits='') - consistency with AGI 148 | * agi_setlanguage($language="en") -> exec_setlanguage($language='en') - consistency with AGI 149 | * text2wav($text) -> text2wav($text, $escape_digits='', $frequency=8000) - consistency with AGI 150 | * phpagi_error_handler($errno, $errstr, $errfile, $errline) -> phpagi_error_handler($level, $message, $file, $line, $context) - I needed better error handling 151 | 152 | August 29, 2004 - 153 | * Fixed db_get now returns a value 154 | * Fixed db_put now stores a value 155 | * Added enum_lookup, requires external "dig" utility. 156 | * Tweak con_print_r now dumps arrays, displays variable type. 157 | * Added enum_txtlookup, requires external "dig" utility". 158 | * Added parse_callerid, thanks to http://www.sbuehl.com/projects/asterisk/asterisk-howto-3.html 159 | 160 | August 26, 2004 - 161 | * Added agi_verbose 162 | * Added debug config-option to shut conlog up 163 | * Added agi_saydigits, agi_saynumber, agi_saytime, agi_setlanguage 164 | 165 | April 8, 2004 - v1.5 internal 166 | * Added agi_goto - David Croft 167 | * Added this changelog (maintain it eh?) - Matthew Asham 168 | * Added quote fixing to conlog - David Croft 169 | * Added new parameter to AGI constructor, $configopt array - Matthew Asham 170 | - Added phpagi_error_handler - David Croft 171 | 172 | 173 | 174 | March 20, 2004 - v1.5 175 | * Fixed buffering bugs - David Croft 176 | * added demo app. - Matthew Asham 177 | * misc stuff. - Matthew Asham 178 | 179 | November 2003 180 | * Initial version 181 | 182 | -------------------------------------------------------------------------------- /docs/README.phpagi: -------------------------------------------------------------------------------- 1 | phpagi: an AGI class written in PHP 2 | 3 | Matthew Asham 4 | https://github.com/welltime/phpagi 5 | 6 | Original version: 7 | http://phpagi.sourceforge.net/ 8 | 9 | Contributions by: 10 | Florian Overkamp 11 | David Eder 12 | Roland Hu 13 | Others 14 | 15 | OVERVIEW 16 | -------- 17 | 18 | Include it: 19 | 20 | require 'phpagi.php'; 21 | 22 | Construct it: 23 | 24 | $agi = new AGI(); 25 | 26 | This creates a new AGI object with all the agi vars read in, you're now ready 27 | to rock and roll. 28 | 29 | Answer the line. 30 | 31 | $agi->answer(); 32 | 33 | Play a file. 34 | 35 | $agi->stream_file('somefile.gsm'); 36 | 37 | Record a file. 38 | 39 | $agi->record_file($file, $format, $escape_digits='', $timeout=-1, $beep=false, $silence=NULL) 40 | 41 | There's more, read the class or refer to the PHPAGI website for more information. 42 | 43 | This README is seriousley lacking. 44 | 45 | CONFIGURATION 46 | ------------- 47 | 48 | phpagi supports an ini style configuration file, and run time configuration. 49 | 50 | By default the class reads in the contents of /etc/asterisk/phpagi.conf into 51 | $this->config. The format of the ini file is as follows: 52 | 53 | [examplesection] 54 | foo=bar 55 | bar=dew 56 | dew=pale ale 57 | 58 | This is read into $this->config as: 59 | 60 | $this->config['examplesection']['foo']='bar'; 61 | $this->config['examplesection']['bar]='dew'; 62 | $this->config['examplesection']['dew']='pale ale'; 63 | 64 | 65 | Run time configuration is also supported. Pass an array of variables and values as the 66 | second parameter to the AGI constructor, these fields are stored in $this->config['phpagi']. 67 | 68 | ie: 69 | 70 | $myconfig=array( 71 | "error_handler"="true" 72 | ); 73 | 74 | $agi=new AGI("/etc/asterisk/phpagi.conf",$myconfig); 75 | 76 | 77 | The following run-time configuration options are used by the phpagi class to change behaviour. They 78 | are all contained in the $this->config['phpagi'] array. 79 | 80 | * error_handler - set to "true" (string) to enable php debugging 81 | 82 | 83 | EXAMPLE APPLICATION 84 | ------------------- 85 | 86 | ping.php is an example phpagi application. it asks for an IP address to be 87 | entered, and reads back the results of a "ping" summary. 88 | 89 | to use it, you'll need festival installed. see http://www.voip-info.org/tiki-index.php?page=Asterisk+festival+installation 90 | for all the groovy bits to get festival working with asterisk, then configure 91 | phpagi to use it too: 92 | 93 | in /etc/asterisk/phpagi.conf: 94 | 95 | [festival] 96 | text2wave=/usr/src/festival/bin/text2wave 97 | tempdir=/var/lib/asterisk/sounds/tmp/ 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/README.phpagi-asmanager: -------------------------------------------------------------------------------- 1 | phpagi-asmanager: an Asterisk Manager class written in PHP 2 | 3 | Matthew Asham 4 | https://github.com/welltime/phpagi 5 | 6 | Original version: 7 | http://phpagi.sourceforge.net/ 8 | 9 | ------------------------------------------------------------------------------ 10 | 11 | ------------------------------------------------------------------------------ 12 | SECURITY 13 | ------------------------------------------------------------------------------ 14 | 15 | Validation: 16 | 17 | ******If asterisk is running as root, the manager interface may allow the 18 | execution of arbitrary shell commands as root. If the user can update any 19 | configuration file that can execute arbitrary command (like the dialplan), 20 | the system may be compromised. 21 | 22 | Also, look out for command injection. Consider the following example: 23 | 24 | $as->Events($_POST['events_status']); 25 | 26 | We expect either 'on' or 'off', but the attacker uses: 27 | 28 | "\r\n\r\nAction: Command\r\nCommand: database put forward 54321 19005551212"; 29 | 30 | 31 | Validation is a *must* for all user data. 32 | 33 | 34 | Username and Secret: 35 | 36 | Storing the username and secret in the config file will isolate them from your 37 | code. 38 | 39 | Isolation of username and secret in the config file does not mean that the 40 | script cannot simple read the config file. The config file must be readable 41 | by the script. 42 | 43 | 44 | CREATING A NEW INSTANCE OF THE CLASS 45 | ------------------------------------------------------------------------------ 46 | 47 | The class can be created standalone of phpagi.php, or through phpagi. 48 | 49 | STANDALONE: 50 | 51 | require "phpagi-asmanager.php"; 52 | 53 | $as = new AGI_AsteriskManager(); 54 | 55 | FROM PHPAGI: 56 | 57 | require "phpagi.php"; 58 | 59 | $agi = new AGI(); 60 | $as = $agi->new_AsteriskManager(); 61 | 62 | Notes: 63 | 64 | * If the class is created using $agi->new_AsteriskManager(), 65 | AGI_AsteriskManager will use the parent phpagi for logging to the Asterisk 66 | console. 67 | 68 | * phpagi.php will include phpagi-asmanager.php by itself. 69 | * If phpagi-asmanager.php is included _before_ phpagi.php, phpagi.php will 70 | not attempt to re-include it. 71 | * If phpagi.php tries to include phpagi-asmanager.php but is unable to do 72 | so, an error will be echoed to the asterisk console and the script will 73 | continue running normally. in this case the return value of 74 | new_AsteriskManager() will be FALSE. 75 | 76 | ------------------------------------------------------------------------------ 77 | CONFIGURATION 78 | ------------------------------------------------------------------------------ 79 | 80 | phpagi-asmanager uses the same configuration file as phpagi.conf (usually 81 | /etc/asterisk/phpagi.conf). All configuration information specific to 82 | phpagi-asmanager is contained in the [asmanager] section of the .conf file. 83 | 84 | supported directives: 85 | 86 | [asmanager] 87 | # server to connect to 88 | server=localhost 89 | 90 | # default manager port 91 | port=5038 92 | 93 | #username for login 94 | username=me_and_only_me 95 | 96 | #password for login 97 | secret=i_am_not_telling 98 | 99 | 100 | 101 | ------------------------------------------------------------------------------ 102 | CONNECTING 103 | ------------------------------------------------------------------------------ 104 | 105 | $res = $as->connect("localhost", "username", "password"); 106 | if($res == FALSE) { 107 | echo "Connection failed.\n"; 108 | } 109 | elseif($res == TRUE){ 110 | echo "Connection established.\n"; 111 | } 112 | 113 | A port can also be specified for the hostname. eg: 114 | 115 | $res = $as->connect("my.asterisk.server:1234", "username", "port"); 116 | 117 | If the no parameters are specified, the defaults from the config will be used. 118 | 119 | 120 | ------------------------------------------------------------------------------ 121 | DISCONNECTING 122 | ------------------------------------------------------------------------------ 123 | 124 | $as->disconnect(); 125 | 126 | ------------------------------------------------------------------------------ 127 | SENDING REQUESTS 128 | ------------------------------------------------------------------------------ 129 | 130 | $as->send_request($eventname, $arrayofparameterstopass); 131 | 132 | send_request() calls wait_request and returns an array of returned data from 133 | the manager. If something went wrong, it returns false. 134 | 135 | wait_request() shouldn't need to be called from a script directly unless you 136 | are implementing merely an event listener. 137 | 138 | wait_request() will also detect events and dispatch any registered event 139 | handlers for the event. 140 | 141 | examples: 142 | 143 | $res = $as->send_request('EventName', 144 | array('Channel'=>'Zap/1/16045551212', 145 | 'SomeParameter'=>'data')); 146 | echo "Dump of returned data:\n"; 147 | foreach($res as $var=>$val) 148 | echo "$var = $val\n"; 149 | 150 | 151 | $res['Response'] will generally be 'Success' on success and 'Error' on 152 | failure. But this is not always true. If $res['Response'] == 'Follows', a 153 | multi-line response will be stored in $res['data']. 154 | 155 | Several manager commands have been aliased for convenience. See below. 156 | 157 | ------------------------------------------------------------------------------ 158 | EVENTS 159 | ------------------------------------------------------------------------------ 160 | 161 | TODO: non-blocking socket i/o. 162 | 163 | The class uses event callbacks to process events received from the manager. 164 | 165 | The event callback prototype looks like: 166 | 167 | function dump_event($ecode, $data, $server, $port) 168 | { 169 | echo "received event '$ecode' from $server:$port\n"; 170 | print_r($data); 171 | } 172 | 173 | To register an event call back: 174 | 175 | $as->add_event_handler('eventname', 'eventfunction'); 176 | 177 | eg: 178 | 179 | $as->add_event_handler('registry', 'dump_event'); 180 | 181 | 182 | The special eventname "*" can also be registered. any eventname not 183 | specifically registered will be handled by the "*" handler. If no "*" handler 184 | is defined, the event will be silently ignored. 185 | 186 | 187 | ------------------------------------------------------------------------------ 188 | PRECANNED FUNCTIONS 189 | ------------------------------------------------------------------------------ 190 | 191 | The following Manager functions have been aliased for convenience: 192 | 193 | AbsoluteTimeout 194 | ChangeMonitor 195 | Command 196 | Events 197 | ExtensionState 198 | GetVar 199 | Hangup 200 | IAXPeers 201 | ListCommands 202 | Logoff 203 | MailboxCount 204 | MailboxStatus 205 | Monitor 206 | Originate 207 | ParkedCalls 208 | Ping 209 | Queues 210 | QueueStatus 211 | Redirect 212 | SetCDRUserField 213 | SetVar 214 | SIPpeers 215 | Status 216 | StopMontor 217 | ZapDialOffhook 218 | ZapDNDoff 219 | ZapDNDon 220 | ZapHangup 221 | ZapTransfer 222 | 223 | -------------------------------------------------------------------------------- /docs/README.phpagi-fastagi: -------------------------------------------------------------------------------- 1 | First, in /etc/services, at this line: 2 | 3 | fastagi 4573/tcp # Asterisk AGI 4 | 5 | 6 | 7 | Second, create /etc/xnetd.d/fastagi with: 8 | 9 | # default: off 10 | # description: fastagi is a remote AGI interface 11 | service fastagi 12 | { 13 | socket_type = stream 14 | user = root 15 | group = nobody 16 | server = 17 | wait = no 18 | protocol = tcp 19 | bind = 127.0.0.1 20 | disable = no 21 | } 22 | 23 | Make sure you set the path to the phpagi-fastagi.php script. Set the user 24 | and group to a non-root user if none of your scripts need root access. You 25 | might consider using posix_setuid and friends to reduce privileges. Change 26 | the bind address to your outbound IP address or to 0.0.0.0 to allow anyone 27 | to connect. Be sure to read up about xinetd and take advantage of security 28 | features it provides. Fast AGI doesn't provide authentification. It's up 29 | to you to keep unwanted visitors from extracting information from your AGI 30 | implementation. 31 | 32 | 33 | Third, write your code. 34 | 35 | Take special notice of how fastagi.php works: 36 | 37 | 1. $fastagi is initialized as a new AGI. 38 | 2. The script determines which script was requested. 39 | 3. The script is called using 40 | reqire_once($fastagi->request['agi_request']). 41 | 4. Your script takes over. You must not create a new AGI, but 42 | insead use the $fastagi instance that has already been created. 43 | 44 | In your dialplan: 45 | 46 | exten => 5551212, 1, Agi(agi://127.0.0.1/myscript.php) 47 | -------------------------------------------------------------------------------- /docs/fastagi.xinetd: -------------------------------------------------------------------------------- 1 | # default: off 2 | # description: fastagi is a remote AGI interface 3 | service fastagi 4 | { 5 | socket_type = stream 6 | user = root 7 | group = nobody 8 | server = /var/lib/asterisk/agi-bin/phpagi/phpagi-fastagi.php 9 | wait = no 10 | protocol = tcp 11 | bind = 127.0.0.1 12 | disable = no 13 | } 14 | 15 | -------------------------------------------------------------------------------- /docs/phpagi.example.conf: -------------------------------------------------------------------------------- 1 | ; example phpagi.conf 2 | 3 | [phpagi] 4 | debug=true ; enable debuging 5 | error_handler=true ; use internal error handler 6 | admin=errors@mydomain.com ; mail errors to 7 | hostname=sip.mydomain.com ; host name of this server 8 | tempdir=/var/spool/asterisk/tmp/ ; temporary directory for storing temporary output 9 | 10 | [asmanager] 11 | server=localhost ; server to connect to 12 | port=5038 ; default manager port 13 | username=me_and_only_me ; username for login 14 | secret=i_am_not_telling ; password for login 15 | 16 | [fastagi] 17 | setuid=true ; drop privileges to owner of script 18 | basedir=/var/lib/asterisk/agi-bin/ ; path to script folder 19 | 20 | [festival] ; text to speech engine 21 | text2wave=/usr/bin/text2wave ; path to text2wave binary 22 | 23 | [cepstral] ; alternate text to speech engine 24 | swift=/opt/swift/bin/swift ; path to switft binary 25 | voice=David ; default voice 26 | 27 | -------------------------------------------------------------------------------- /mkdocs.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/phpagi-asmanager.php: -------------------------------------------------------------------------------- 1 | , David Eder and others 10 | * All Rights Reserved. 11 | * 12 | * This software is released under the terms of the GNU Lesser General Public License v2.1 13 | * A copy of which is available from http://www.gnu.org/copyleft/lesser.html 14 | * 15 | * We would be happy to list your phpagi based application on the phpagi 16 | * website. Drop me an Email if you'd like us to list your program. 17 | * 18 | * @package phpAGI 19 | * @version 2.0 20 | */ 21 | 22 | 23 | /** 24 | * Written for PHP 4.3.4, should work with older PHP 4.x versions. 25 | * Please submit bug reports, patches, etc to https://github.com/welltime/phpagi 26 | * 27 | */ 28 | 29 | if(!class_exists('AGI')) 30 | { 31 | require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); 32 | } 33 | 34 | /** 35 | * Asterisk Manager class 36 | * 37 | * @link http://www.voip-info.org/wiki-Asterisk+config+manager.conf 38 | * @link http://www.voip-info.org/wiki-Asterisk+manager+API 39 | * @example examples/sip_show_peer.php Get information about a sip peer 40 | * @package phpAGI 41 | */ 42 | class AGI_AsteriskManager 43 | { 44 | /** 45 | * Config variables 46 | * 47 | * @var array 48 | * @access public 49 | */ 50 | public $config; 51 | 52 | /** 53 | * Socket 54 | * 55 | * @access public 56 | */ 57 | public $socket = null; 58 | 59 | /** 60 | * Server we are connected to 61 | * 62 | * @access public 63 | * @var string 64 | */ 65 | public $server; 66 | 67 | /** 68 | * Port on the server we are connected to 69 | * 70 | * @access public 71 | * @var integer 72 | */ 73 | public $port; 74 | 75 | /** 76 | * Parent AGI 77 | * 78 | * @access private 79 | * @var AGI 80 | */ 81 | public $pagi = false; 82 | 83 | /** 84 | * Event Handlers 85 | * 86 | * @access private 87 | * @var array 88 | */ 89 | private $event_handlers; 90 | 91 | private $_buffer = null; 92 | 93 | /** 94 | * Whether we're successfully logged in 95 | * 96 | * @access private 97 | * @var boolean 98 | */ 99 | private $_logged_in = false; 100 | 101 | public function setPagi(&$agi) 102 | { 103 | $this->pagi = $agi; 104 | } 105 | 106 | /** 107 | * Constructor 108 | * 109 | * @param string $config is the name of the config file to parse or a parent agi from which to read the config 110 | * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['asmanager'] 111 | */ 112 | function __construct($config=null, $optconfig=array()) 113 | { 114 | // load config 115 | if(!is_null($config) && file_exists($config)) 116 | $this->config = parse_ini_file($config, true); 117 | elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) 118 | $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); 119 | 120 | // If optconfig is specified, stuff vals and vars into 'asmanager' config array. 121 | foreach($optconfig as $var=>$val) 122 | $this->config['asmanager'][$var] = $val; 123 | 124 | // add default values to config for uninitialized values 125 | if(!isset($this->config['asmanager']['server'])) $this->config['asmanager']['server'] = 'localhost'; 126 | if(!isset($this->config['asmanager']['port'])) $this->config['asmanager']['port'] = 5038; 127 | if(!isset($this->config['asmanager']['username'])) $this->config['asmanager']['username'] = 'phpagi'; 128 | if(!isset($this->config['asmanager']['secret'])) $this->config['asmanager']['secret'] = 'phpagi'; 129 | if(!isset($this->config['asmanager']['write_log'])) $this->config['asmanager']['write_log'] = false; 130 | } 131 | 132 | /** 133 | * Send a request 134 | * 135 | * @param string $action 136 | * @param array $parameters 137 | * @return array of parameters 138 | */ 139 | function send_request($action, $parameters=array()) 140 | { 141 | $req = "Action: $action\r\n"; 142 | $actionid = null; 143 | foreach ($parameters as $var=>$val) { 144 | if (is_array($val)) { 145 | foreach ($val as $line) { 146 | $req .= "$var: $line\r\n"; 147 | } 148 | } else { 149 | $req .= "$var: $val\r\n"; 150 | if (strtolower($var) == "actionid") { 151 | $actionid = $val; 152 | } 153 | } 154 | } 155 | if (!$actionid) { 156 | $actionid = $this->ActionID(); 157 | $req .= "ActionID: $actionid\r\n"; 158 | } 159 | $req .= "\r\n"; 160 | 161 | fwrite($this->socket, $req); 162 | 163 | return $this->wait_response(false, $actionid); 164 | } 165 | 166 | function read_one_msg($allow_timeout = false) 167 | { 168 | $type = null; 169 | 170 | do { 171 | $buf = fgets($this->socket, 4096); 172 | if (false === $buf) { 173 | throw new Exception("Error reading from AMI socket"); 174 | } 175 | $this->_buffer .= $buf; 176 | 177 | $pos = strpos($this->_buffer, "\r\n\r\n"); 178 | if (false !== $pos) { 179 | // there's a full message in the buffer 180 | break; 181 | } 182 | } while (!feof($this->socket)); 183 | 184 | $msg = substr($this->_buffer, 0, $pos); 185 | $this->_buffer = substr($this->_buffer, $pos+4); 186 | 187 | $msgarr = explode("\r\n", $msg); 188 | 189 | $parameters = array(); 190 | 191 | $r = explode(': ', $msgarr[0]); 192 | $type = strtolower($r[0]); 193 | 194 | if ($r[1] == 'Success' || $r[1] == 'Follows') { 195 | $m = explode(': ', $msgarr[2]); 196 | $msgarr_tmp = $msgarr; 197 | $str = array_pop($msgarr); 198 | $lastline = strpos($str, '--END COMMAND--'); 199 | if (false !== $lastline) { 200 | $parameters['data'] = substr($str, 0, $lastline-1); // cut '\n' too 201 | } else { 202 | if ($m[1] == 'Command output follows') { 203 | $n = 3; 204 | $c = count($msgarr_tmp) - 1; 205 | $output = explode(': ', $msgarr_tmp[3]); 206 | if ($output[1]) { 207 | $data = $output[1]; 208 | while ($n++<$c) { 209 | $output = explode(': ', $msgarr_tmp[$n]); 210 | if ($output[1]) { 211 | $data .= "\n".$output[1]; 212 | } 213 | } 214 | $parameters['data'] = $data; 215 | } 216 | } 217 | } 218 | } 219 | 220 | foreach ($msgarr as $num=>$str) { 221 | $kv = explode(':', $str, 2); 222 | if (!isset($kv[1])) { 223 | $kv[1] = ""; 224 | } 225 | $key = trim($kv[0]); 226 | $val = trim($kv[1]); 227 | $parameters[$key] = $val; 228 | } 229 | 230 | // process response 231 | switch($type) 232 | { 233 | case '': // timeout occured 234 | $timeout = $allow_timeout; 235 | break; 236 | case 'event': 237 | $this->process_event($parameters); 238 | break; 239 | case 'response': 240 | break; 241 | default: 242 | $this->log('Unhandled response packet from Manager: ' . print_r($parameters, true)); 243 | break; 244 | } 245 | 246 | return $parameters; 247 | } 248 | 249 | /** 250 | * Wait for a response 251 | * 252 | * If a request was just sent, this will return the response. 253 | * Otherwise, it will loop forever, handling events. 254 | * 255 | * XXX this code is slightly better then the original one 256 | * however it's still totally screwed up and needs to be rewritten, 257 | * for two reasons at least: 258 | * 1. it does not handle socket errors in any way 259 | * 2. it is terribly synchronous, esp. with eventlists, 260 | * i.e. your code is blocked on waiting until full responce is received 261 | * 262 | * @param boolean $allow_timeout if the socket times out, return an empty array 263 | * @return array of parameters, empty on timeout 264 | */ 265 | function wait_response($allow_timeout = false, $actionid = null) 266 | { 267 | $res = array(); 268 | if ($actionid) { 269 | do { 270 | $res = $this->read_one_msg($allow_timeout); 271 | } while (!( isset($res['ActionID']) && $res['ActionID']==$actionid )); 272 | } else { 273 | $res = $this->read_one_msg($allow_timeout); 274 | return $res; 275 | } 276 | 277 | if (isset($res['EventList']) && $res['EventList']=='start') { 278 | $evlist = array(); 279 | do { 280 | $res = $this->wait_response(false, $actionid); 281 | if (isset($res['EventList']) && $res['EventList']=='Complete') 282 | break; 283 | else 284 | $evlist[] = $res; 285 | } while(true); 286 | $res['events'] = $evlist; 287 | } 288 | 289 | return $res; 290 | } 291 | 292 | 293 | /** 294 | * Connect to Asterisk 295 | * 296 | * @example examples/sip_show_peer.php Get information about a sip peer 297 | * 298 | * @param string $server 299 | * @param string $username 300 | * @param string $secret 301 | * @return boolean true on success 302 | */ 303 | function connect($server=null, $username=null, $secret=null) 304 | { 305 | // use config if not specified 306 | if(is_null($server)) $server = $this->config['asmanager']['server']; 307 | if(is_null($username)) $username = $this->config['asmanager']['username']; 308 | if(is_null($secret)) $secret = $this->config['asmanager']['secret']; 309 | 310 | // get port from server if specified 311 | if(strpos($server, ':') !== false) 312 | { 313 | $c = explode(':', $server); 314 | $this->server = $c[0]; 315 | $this->port = $c[1]; 316 | } 317 | else 318 | { 319 | $this->server = $server; 320 | $this->port = $this->config['asmanager']['port']; 321 | } 322 | 323 | // connect the socket 324 | $errno = $errstr = null; 325 | $this->socket = @fsockopen($this->server, $this->port, $errno, $errstr); 326 | if($this->socket == false) 327 | { 328 | $this->log("Unable to connect to manager {$this->server}:{$this->port} ($errno): $errstr"); 329 | return false; 330 | } 331 | 332 | // read the header 333 | $str = fgets($this->socket); 334 | if($str == false) 335 | { 336 | // a problem. 337 | $this->log("Asterisk Manager header not received."); 338 | return false; 339 | } 340 | else 341 | { 342 | // note: don't $this->log($str) until someone looks to see why it mangles the logging 343 | } 344 | 345 | // login 346 | $res = $this->send_request('login', array('Username'=>$username, 'Secret'=>$secret)); 347 | if($res['Response'] != 'Success') 348 | { 349 | $this->_logged_in = false; 350 | $this->log("Failed to login."); 351 | $this->disconnect(); 352 | return false; 353 | } 354 | $this->_logged_in = true; 355 | return true; 356 | } 357 | 358 | /** 359 | * Disconnect 360 | * 361 | * @example examples/sip_show_peer.php Get information about a sip peer 362 | */ 363 | function disconnect() 364 | { 365 | if($this->_logged_in==true) 366 | $this->logoff(); 367 | fclose($this->socket); 368 | } 369 | 370 | // ********************************************************************************************************* 371 | // ** COMMANDS ** 372 | // ********************************************************************************************************* 373 | 374 | /** 375 | * Set Absolute Timeout 376 | * 377 | * Hangup a channel after a certain time. 378 | * 379 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+AbsoluteTimeout 380 | * @param string $channel Channel name to hangup 381 | * @param integer $timeout Maximum duration of the call (sec) 382 | */ 383 | function AbsoluteTimeout($channel, $timeout) 384 | { 385 | return $this->send_request('AbsoluteTimeout', array('Channel'=>$channel, 'Timeout'=>$timeout)); 386 | } 387 | 388 | /** 389 | * Change monitoring filename of a channel 390 | * 391 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ChangeMonitor 392 | * @param string $channel the channel to record. 393 | * @param string $file the new name of the file created in the monitor spool directory. 394 | */ 395 | function ChangeMonitor($channel, $file) 396 | { 397 | return $this->send_request('ChangeMontior', array('Channel'=>$channel, 'File'=>$file)); 398 | } 399 | 400 | /** 401 | * Execute Command 402 | * 403 | * @example examples/sip_show_peer.php Get information about a sip peer 404 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Command 405 | * @link http://www.voip-info.org/wiki-Asterisk+CLI 406 | * @param string $command 407 | * @param string $actionid message matching variable 408 | */ 409 | function Command($command, $actionid=null) 410 | { 411 | $parameters = array('Command'=>$command); 412 | if($actionid) $parameters['ActionID'] = $actionid; 413 | return $this->send_request('Command', $parameters); 414 | } 415 | 416 | /** 417 | * Enable/Disable sending of events to this manager 418 | * 419 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Events 420 | * @param string $eventmask is either 'on', 'off', or 'system,call,log' 421 | */ 422 | function Events($eventmask) 423 | { 424 | return $this->send_request('Events', array('EventMask'=>$eventmask)); 425 | } 426 | 427 | /** 428 | * Generate random ActionID 429 | **/ 430 | function ActionID() 431 | { 432 | return "A".sprintf(rand(),"%6d"); 433 | } 434 | 435 | /** 436 | * 437 | * DBGet 438 | * http://www.voip-info.org/wiki/index.php?page=Asterisk+Manager+API+Action+DBGet 439 | * @param string $family key family 440 | * @param string $key key name 441 | **/ 442 | function DBGet($family, $key, $actionid = null) 443 | { 444 | $parameters = array('Family'=>$family, 'Key'=>$key); 445 | if($actionid == null) 446 | $actionid = $this->ActionID(); 447 | $parameters['ActionID'] = $actionid; 448 | $response = $this->send_request("DBGet", $parameters); 449 | if($response['Response'] == "Success") 450 | { 451 | $response = $this->wait_response(false, $actionid); 452 | return $response['Val']; 453 | } 454 | return ""; 455 | } 456 | 457 | /** 458 | * Check Extension Status 459 | * 460 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ExtensionState 461 | * @param string $exten Extension to check state on 462 | * @param string $context Context for extension 463 | * @param string $actionid message matching variable 464 | */ 465 | function ExtensionState($exten, $context, $actionid=null) 466 | { 467 | $parameters = array('Exten'=>$exten, 'Context'=>$context); 468 | if($actionid) $parameters['ActionID'] = $actionid; 469 | return $this->send_request('ExtensionState', $parameters); 470 | } 471 | 472 | /** 473 | * Gets a Channel Variable 474 | * 475 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+GetVar 476 | * @link http://www.voip-info.org/wiki-Asterisk+variables 477 | * @param string $channel Channel to read variable from 478 | * @param string $variable 479 | * @param string $actionid message matching variable 480 | */ 481 | function GetVar($channel, $variable, $actionid=null) 482 | { 483 | $parameters = array('Channel'=>$channel, 'Variable'=>$variable); 484 | if($actionid) $parameters['ActionID'] = $actionid; 485 | return $this->send_request('GetVar', $parameters); 486 | } 487 | 488 | /** 489 | * Hangup Channel 490 | * 491 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Hangup 492 | * @param string $channel The channel name to be hungup 493 | */ 494 | function Hangup($channel) 495 | { 496 | return $this->send_request('Hangup', array('Channel'=>$channel)); 497 | } 498 | 499 | /** 500 | * List IAX Peers 501 | * 502 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+IAXpeers 503 | */ 504 | function IAXPeers() 505 | { 506 | return $this->send_request('IAXPeers'); 507 | } 508 | 509 | /** 510 | * List available manager commands 511 | * 512 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ListCommands 513 | * @param string $actionid message matching variable 514 | */ 515 | function ListCommands($actionid=null) 516 | { 517 | if($actionid) 518 | return $this->send_request('ListCommands', array('ActionID'=>$actionid)); 519 | else 520 | return $this->send_request('ListCommands'); 521 | } 522 | 523 | /** 524 | * Logoff Manager 525 | * 526 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Logoff 527 | */ 528 | function Logoff() 529 | { 530 | return $this->send_request('Logoff'); 531 | } 532 | 533 | /** 534 | * Check Mailbox Message Count 535 | * 536 | * Returns number of new and old messages. 537 | * Message: Mailbox Message Count 538 | * Mailbox: 539 | * NewMessages: 540 | * OldMessages: 541 | * 542 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxCount 543 | * @param string $mailbox Full mailbox ID @ 544 | * @param string $actionid message matching variable 545 | */ 546 | function MailboxCount($mailbox, $actionid=null) 547 | { 548 | $parameters = array('Mailbox'=>$mailbox); 549 | if($actionid) $parameters['ActionID'] = $actionid; 550 | return $this->send_request('MailboxCount', $parameters); 551 | } 552 | 553 | /** 554 | * Check Mailbox 555 | * 556 | * Returns number of messages. 557 | * Message: Mailbox Status 558 | * Mailbox: 559 | * Waiting: 560 | * 561 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxStatus 562 | * @param string $mailbox Full mailbox ID @ 563 | * @param string $actionid message matching variable 564 | */ 565 | function MailboxStatus($mailbox, $actionid=null) 566 | { 567 | $parameters = array('Mailbox'=>$mailbox); 568 | if($actionid) $parameters['ActionID'] = $actionid; 569 | return $this->send_request('MailboxStatus', $parameters); 570 | } 571 | 572 | /** 573 | * Monitor a channel 574 | * 575 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor 576 | * @param string $channel 577 | * @param string $file 578 | * @param string $format 579 | * @param boolean $mix 580 | */ 581 | function Monitor($channel, $file=null, $format=null, $mix=null) 582 | { 583 | $parameters = array('Channel'=>$channel); 584 | if($file) $parameters['File'] = $file; 585 | if($format) $parameters['Format'] = $format; 586 | if(!is_null($file)) $parameters['Mix'] = ($mix) ? 'true' : 'false'; 587 | return $this->send_request('Monitor', $parameters); 588 | } 589 | 590 | /** 591 | * Originate Call 592 | * 593 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Originate 594 | * @param string $channel Channel name to call 595 | * @param string $exten Extension to use (requires 'Context' and 'Priority') 596 | * @param string $context Context to use (requires 'Exten' and 'Priority') 597 | * @param string $priority Priority to use (requires 'Exten' and 'Context') 598 | * @param string $application Application to use 599 | * @param string $data Data to use (requires 'Application') 600 | * @param integer $timeout How long to wait for call to be answered (in ms) 601 | * @param string $callerid Caller ID to be set on the outgoing channel 602 | * @param string $variable Channel variable to set (VAR1=value1|VAR2=value2) 603 | * @param string $account Account code 604 | * @param boolean $async true fast origination 605 | * @param string $actionid message matching variable 606 | */ 607 | function Originate($channel, 608 | $exten=null, $context=null, $priority=null, 609 | $application=null, $data=null, 610 | $timeout=null, $callerid=null, $variable=null, $account=null, $async=null, $actionid=null) 611 | { 612 | $parameters = array('Channel'=>$channel); 613 | 614 | if($exten) $parameters['Exten'] = $exten; 615 | if($context) $parameters['Context'] = $context; 616 | if($priority) $parameters['Priority'] = $priority; 617 | 618 | if($application) $parameters['Application'] = $application; 619 | if($data) $parameters['Data'] = $data; 620 | 621 | if($timeout) $parameters['Timeout'] = $timeout; 622 | if($callerid) $parameters['CallerID'] = $callerid; 623 | if($variable) $parameters['Variable'] = $variable; 624 | if($account) $parameters['Account'] = $account; 625 | if(!is_null($async)) $parameters['Async'] = ($async) ? 'true' : 'false'; 626 | if($actionid) $parameters['ActionID'] = $actionid; 627 | 628 | return $this->send_request('Originate', $parameters); 629 | } 630 | 631 | /** 632 | * List parked calls 633 | * 634 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ParkedCalls 635 | * @param string $actionid message matching variable 636 | */ 637 | function ParkedCalls($actionid=null) 638 | { 639 | if($actionid) 640 | return $this->send_request('ParkedCalls', array('ActionID'=>$actionid)); 641 | else 642 | return $this->send_request('ParkedCalls'); 643 | } 644 | 645 | /** 646 | * Ping 647 | * 648 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Ping 649 | */ 650 | function Ping() 651 | { 652 | return $this->send_request('Ping'); 653 | } 654 | 655 | /** 656 | * Queue Add 657 | * 658 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueAdd 659 | * @param string $queue 660 | * @param string $interface 661 | * @param integer $penalty 662 | * @param string $memberName 663 | */ 664 | function QueueAdd($queue, $interface, $penalty=0, $memberName = false) 665 | { 666 | $parameters = array('Queue'=>$queue, 'Interface'=>$interface); 667 | if($penalty) $parameters['Penalty'] = $penalty; 668 | if($memberName) $parameters["MemberName"] = $memberName; 669 | return $this->send_request('QueueAdd', $parameters); 670 | } 671 | 672 | /** 673 | * Queue Remove 674 | * 675 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueRemove 676 | * @param string $queue 677 | * @param string $interface 678 | */ 679 | function QueueRemove($queue, $interface) 680 | { 681 | return $this->send_request('QueueRemove', array('Queue'=>$queue, 'Interface'=>$interface)); 682 | } 683 | 684 | function QueueReload() 685 | { 686 | return $this->send_request('QueueReload'); 687 | } 688 | 689 | /** 690 | * Queues 691 | * 692 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Queues 693 | */ 694 | function Queues() 695 | { 696 | return $this->send_request('Queues'); 697 | } 698 | 699 | /** 700 | * Queue Status 701 | * 702 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueStatus 703 | * @param string $actionid message matching variable 704 | */ 705 | function QueueStatus($actionid=null) 706 | { 707 | if($actionid) 708 | return $this->send_request('QueueStatus', array('ActionID'=>$actionid)); 709 | else 710 | return $this->send_request('QueueStatus'); 711 | } 712 | 713 | /** 714 | * Redirect 715 | * 716 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Redirect 717 | * @param string $channel 718 | * @param string $extrachannel 719 | * @param string $exten 720 | * @param string $context 721 | * @param string $priority 722 | */ 723 | function Redirect($channel, $extrachannel, $exten, $context, $priority) 724 | { 725 | return $this->send_request('Redirect', array('Channel'=>$channel, 'ExtraChannel'=>$extrachannel, 'Exten'=>$exten, 726 | 'Context'=>$context, 'Priority'=>$priority)); 727 | } 728 | 729 | function Atxfer($channel, $exten, $context, $priority) 730 | { 731 | return $this->send_request('Atxfer', array('Channel'=>$channel, 'Exten'=>$exten, 732 | 'Context'=>$context, 'Priority'=>$priority)); 733 | } 734 | 735 | /** 736 | * Set the CDR UserField 737 | * 738 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetCDRUserField 739 | * @param string $userfield 740 | * @param string $channel 741 | * @param string $append 742 | */ 743 | function SetCDRUserField($userfield, $channel, $append=null) 744 | { 745 | $parameters = array('UserField'=>$userfield, 'Channel'=>$channel); 746 | if($append) $parameters['Append'] = $append; 747 | return $this->send_request('SetCDRUserField', $parameters); 748 | } 749 | 750 | /** 751 | * Set Channel Variable 752 | * 753 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetVar 754 | * @param string $channel Channel to set variable for 755 | * @param string $variable name 756 | * @param string $value 757 | */ 758 | function SetVar($channel, $variable, $value) 759 | { 760 | return $this->send_request('SetVar', array('Channel'=>$channel, 'Variable'=>$variable, 'Value'=>$value)); 761 | } 762 | 763 | /** 764 | * Channel Status 765 | * 766 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Status 767 | * @param string $channel 768 | * @param string $actionid message matching variable 769 | */ 770 | function Status($channel, $actionid=null) 771 | { 772 | $parameters = array('Channel'=>$channel); 773 | if($actionid) $parameters['ActionID'] = $actionid; 774 | return $this->send_request('Status', $parameters); 775 | } 776 | 777 | /** 778 | * Stop monitoring a channel 779 | * 780 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+StopMonitor 781 | * @param string $channel 782 | */ 783 | function StopMonitor($channel) 784 | { 785 | return $this->send_request('StopMonitor', array('Channel'=>$channel)); 786 | } 787 | 788 | /** 789 | * Dial over Zap channel while offhook 790 | * 791 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDialOffhook 792 | * @param string $zapchannel 793 | * @param string $number 794 | */ 795 | function ZapDialOffhook($zapchannel, $number) 796 | { 797 | return $this->send_request('ZapDialOffhook', array('ZapChannel'=>$zapchannel, 'Number'=>$number)); 798 | } 799 | 800 | /** 801 | * Toggle Zap channel Do Not Disturb status OFF 802 | * 803 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDoff 804 | * @param string $zapchannel 805 | */ 806 | function ZapDNDoff($zapchannel) 807 | { 808 | return $this->send_request('ZapDNDoff', array('ZapChannel'=>$zapchannel)); 809 | } 810 | 811 | /** 812 | * Toggle Zap channel Do Not Disturb status ON 813 | * 814 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDon 815 | * @param string $zapchannel 816 | */ 817 | function ZapDNDon($zapchannel) 818 | { 819 | return $this->send_request('ZapDNDon', array('ZapChannel'=>$zapchannel)); 820 | } 821 | 822 | /** 823 | * Hangup Zap Channel 824 | * 825 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapHangup 826 | * @param string $zapchannel 827 | */ 828 | function ZapHangup($zapchannel) 829 | { 830 | return $this->send_request('ZapHangup', array('ZapChannel'=>$zapchannel)); 831 | } 832 | 833 | /** 834 | * Transfer Zap Channel 835 | * 836 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapTransfer 837 | * @param string $zapchannel 838 | */ 839 | function ZapTransfer($zapchannel) 840 | { 841 | return $this->send_request('ZapTransfer', array('ZapChannel'=>$zapchannel)); 842 | } 843 | 844 | /** 845 | * Zap Show Channels 846 | * 847 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapShowChannels 848 | * @param string $actionid message matching variable 849 | */ 850 | function ZapShowChannels($actionid=null) 851 | { 852 | if($actionid) 853 | return $this->send_request('ZapShowChannels', array('ActionID'=>$actionid)); 854 | else 855 | return $this->send_request('ZapShowChannels'); 856 | } 857 | 858 | // ********************************************************************************************************* 859 | // ** MISC ** 860 | // ********************************************************************************************************* 861 | 862 | /* 863 | * Log a message 864 | * 865 | * @param string $message 866 | * @param integer $level from 1 to 4 867 | */ 868 | function log($message, $level=1) 869 | { 870 | if($this->pagi != false) 871 | $this->pagi->conlog($message, $level); 872 | elseif($this->config['asmanager']['write_log']) 873 | error_log(date('r') . ' - ' . $message); 874 | } 875 | 876 | /** 877 | * Add event handler 878 | * 879 | * Known Events include ( http://www.voip-info.org/wiki-asterisk+manager+events ) 880 | * Link - Fired when two voice channels are linked together and voice data exchange commences. 881 | * Unlink - Fired when a link between two voice channels is discontinued, for example, just before call completion. 882 | * Newexten - 883 | * Hangup - 884 | * Newchannel - 885 | * Newstate - 886 | * Reload - Fired when the "RELOAD" console command is executed. 887 | * Shutdown - 888 | * ExtensionStatus - 889 | * Rename - 890 | * Newcallerid - 891 | * Alarm - 892 | * AlarmClear - 893 | * Agentcallbacklogoff - 894 | * Agentcallbacklogin - 895 | * Agentlogoff - 896 | * MeetmeJoin - 897 | * MessageWaiting - 898 | * join - 899 | * leave - 900 | * AgentCalled - 901 | * ParkedCall - Fired after ParkedCalls 902 | * Cdr - 903 | * ParkedCallsComplete - 904 | * QueueParams - 905 | * QueueMember - 906 | * QueueStatusEnd - 907 | * Status - 908 | * StatusComplete - 909 | * ZapShowChannels - Fired after ZapShowChannels 910 | * ZapShowChannelsComplete - 911 | * 912 | * @param string $event type or * for default handler 913 | * @param string $callback function 914 | * @return boolean sucess 915 | */ 916 | function add_event_handler($event, $callback) 917 | { 918 | $event = strtolower($event); 919 | if(isset($this->event_handlers[$event])) 920 | { 921 | $this->log("$event handler is already defined, not over-writing."); 922 | return false; 923 | } 924 | $this->event_handlers[$event] = $callback; 925 | return true; 926 | } 927 | /** 928 | * 929 | * Remove event handler 930 | * 931 | * @param string $event type or * for default handler 932 | * @return boolean sucess 933 | **/ 934 | function remove_event_handler($event) 935 | { 936 | $event = strtolower($event); 937 | if(isset($this->event_handlers[$event])) 938 | { 939 | unset($this->event_handlers[$event]); 940 | return true; 941 | } 942 | $this->log("$event handler is not defined."); 943 | return false; 944 | } 945 | 946 | /** 947 | * Process event 948 | * 949 | * @access private 950 | * @param array $parameters 951 | * @return mixed result of event handler or false if no handler was found 952 | */ 953 | function process_event($parameters) 954 | { 955 | $ret = false; 956 | $e = strtolower($parameters['Event']); 957 | $this->log("Got event.. $e"); 958 | 959 | $handler = ''; 960 | if(isset($this->event_handlers[$e])) $handler = $this->event_handlers[$e]; 961 | elseif(isset($this->event_handlers['*'])) $handler = $this->event_handlers['*']; 962 | 963 | if(function_exists($handler)) 964 | { 965 | $this->log("Execute handler $handler"); 966 | $ret = $handler($e, $parameters, $this->server, $this->port); 967 | } elseif (is_array($handler)) { 968 | $ret = call_user_func($handler, $e, $parameters, $this->server, $this->port); 969 | } 970 | else 971 | $this->log("No event handler for event '$e'"); 972 | return $ret; 973 | } 974 | } 975 | ?> 976 | -------------------------------------------------------------------------------- /src/phpagi-fastagi.php: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php -q 2 | , David Eder 11 | * All Rights Reserved. 12 | * 13 | * This software is released under the terms of the GNU Lesser General Public License v2.1 14 | * A copy of which is available from http://www.gnu.org/copyleft/lesser.html 15 | * 16 | * We would be happy to list your phpagi based application on the phpagi 17 | * website. Drop me an Email if you'd like us to list your program. 18 | * 19 | * @package phpAGI 20 | * @version 2.0 21 | * @example docs/fastagi.xinetd Example xinetd config file 22 | */ 23 | 24 | /** 25 | * Written for PHP 4.3.4, should work with older PHP 4.x versions. 26 | * Please submit bug reports, patches, etc to https://github.com/welltime/phpagi 27 | * 28 | */ 29 | 30 | require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); 31 | 32 | $fastagi = new AGI(); 33 | 34 | $fastagi->verbose(print_r($fastagi, true)); 35 | 36 | if(!isset($fastagi->config['fastagi']['basedir'])) 37 | $fastagi->config['fastagi']['basedir'] = dirname(__FILE__); 38 | 39 | // perform some security checks 40 | 41 | $script = $fastagi->config['fastagi']['basedir'] . DIRECTORY_SEPARATOR . $fastagi->request['agi_network_script']; 42 | 43 | // in the same directory (or subdirectory) 44 | $mydir = dirname($fastagi->config['fastagi']['basedir']) . DIRECTORY_SEPARATOR; 45 | $dir = dirname($script) . DIRECTORY_SEPARATOR; 46 | if(substr($dir, 0, strlen($mydir)) != $mydir) 47 | { 48 | $fastagi->conlog("$script is not allowed to execute."); 49 | exit; 50 | } 51 | 52 | // make sure it exists 53 | if(!file_exists($script)) 54 | { 55 | $fastagi->conlog("$script does not exist."); 56 | exit; 57 | } 58 | 59 | // drop privileges 60 | if(isset($fastagi->config['fastagi']['setuid']) && $fastagi->config['fastagi']['setuid']) 61 | { 62 | $owner = fileowner($script); 63 | $group = filegroup($script); 64 | if(!posix_setgid($group) || !posix_setegid($group) || !posix_setuid($owner) || !posix_seteuid($owner)) 65 | { 66 | $fastagi->conlog("failed to lower privileges."); 67 | exit; 68 | } 69 | } 70 | 71 | // make sure script is still readable 72 | if(!is_readable($script)) 73 | { 74 | $fastagi->conlog("$script is not readable."); 75 | exit; 76 | } 77 | 78 | require_once($script); 79 | ?> 80 | -------------------------------------------------------------------------------- /src/phpagi.php: -------------------------------------------------------------------------------- 1 | , David Eder and others 11 | * All Rights Reserved. 12 | * 13 | * This software is released under the terms of the GNU Lesser General Public License v2.1 14 | * A copy of which is available from http://www.gnu.org/copyleft/lesser.html 15 | * 16 | * We would be happy to list your phpagi based application on the phpagi 17 | * website. Drop me an Email if you'd like us to list your program. 18 | * 19 | * 20 | * Written for PHP 4.3.4, should work with older PHP 4.x versions. 21 | * 22 | * Please submit bug reports, patches, etc to https://github.com/welltime/phpagi 23 | * 24 | * 25 | * @package phpAGI 26 | * @version 2.20 27 | */ 28 | 29 | if (!class_exists('AGI_AsteriskManager')) 30 | { 31 | require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi-asmanager.php'); 32 | } 33 | 34 | define('AST_CONFIG_DIR', '/etc/asterisk/'); 35 | define('AST_SPOOL_DIR', '/var/spool/asterisk/'); 36 | define('AST_TMP_DIR', AST_SPOOL_DIR . '/tmp/'); 37 | define('DEFAULT_PHPAGI_CONFIG', AST_CONFIG_DIR . '/phpagi.conf'); 38 | 39 | define('AST_DIGIT_ANY', '0123456789#*'); 40 | 41 | define('AGIRES_OK', 200); 42 | 43 | define('AST_STATE_DOWN', 0); 44 | define('AST_STATE_RESERVED', 1); 45 | define('AST_STATE_OFFHOOK', 2); 46 | define('AST_STATE_DIALING', 3); 47 | define('AST_STATE_RING', 4); 48 | define('AST_STATE_RINGING', 5); 49 | define('AST_STATE_UP', 6); 50 | define('AST_STATE_BUSY', 7); 51 | define('AST_STATE_DIALING_OFFHOOK', 8); 52 | define('AST_STATE_PRERING', 9); 53 | 54 | define('AUDIO_FILENO', 3); // STDERR_FILENO + 1 55 | 56 | /** 57 | * AGI class 58 | * 59 | * @package phpAGI 60 | * @link http://www.voip-info.org/wiki-Asterisk+agi 61 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 62 | * @example examples/input.php Get text input from the user and say it back 63 | * @example examples/ping.php Ping an IP address 64 | */ 65 | class AGI 66 | { 67 | /** 68 | * Request variables read in on initialization. 69 | * 70 | * Often contains any/all of the following: 71 | * agi_request - name of agi script 72 | * agi_channel - current channel 73 | * agi_language - current language 74 | * agi_type - channel type (SIP, ZAP, IAX, ...) 75 | * agi_uniqueid - unique id based on unix time 76 | * agi_callerid - callerID string 77 | * agi_dnid - dialed number id 78 | * agi_rdnis - referring DNIS number 79 | * agi_context - current context 80 | * agi_extension - extension dialed 81 | * agi_priority - current priority 82 | * agi_enhanced - value is 1.0 if started as an EAGI script 83 | * agi_accountcode - set by SetAccount in the dialplan 84 | * agi_network - value is yes if this is a fastagi 85 | * agi_network_script - name of the script to execute 86 | * 87 | * NOTE: program arguments are still in $_SERVER['argv']. 88 | * 89 | * @var array 90 | * @access public 91 | */ 92 | var $request; 93 | 94 | /** 95 | * Config variables 96 | * 97 | * @var array 98 | * @access public 99 | */ 100 | var $config; 101 | 102 | /** 103 | * Asterisk Manager 104 | * 105 | * @var AGI_AsteriskManager 106 | * @access public 107 | */ 108 | var $asmanager; 109 | 110 | /** 111 | * Input Stream 112 | * 113 | * @access private 114 | */ 115 | var $in = null; 116 | 117 | /** 118 | * Output Stream 119 | * 120 | * @access private 121 | */ 122 | var $out = null; 123 | 124 | /** 125 | * Audio Stream 126 | * 127 | * @access public 128 | */ 129 | var $audio = null; 130 | 131 | 132 | /** 133 | * Application option delimiter 134 | * 135 | * @access public 136 | */ 137 | public $option_delim = ","; 138 | 139 | /** 140 | * Constructor 141 | * 142 | * @param string $config is the name of the config file to parse 143 | * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['phpagi'] 144 | */ 145 | function __construct($config=null, $optconfig=array()) 146 | { 147 | // load config 148 | if(!is_null($config) && file_exists($config)) 149 | $this->config = parse_ini_file($config, true); 150 | elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) 151 | $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); 152 | 153 | // If optconfig is specified, stuff vals and vars into 'phpagi' config array. 154 | foreach($optconfig as $var=>$val) 155 | $this->config['phpagi'][$var] = $val; 156 | 157 | // add default values to config for uninitialized values 158 | if(!isset($this->config['phpagi']['error_handler'])) $this->config['phpagi']['error_handler'] = true; 159 | if(!isset($this->config['phpagi']['debug'])) $this->config['phpagi']['debug'] = false; 160 | if(!isset($this->config['phpagi']['admin'])) $this->config['phpagi']['admin'] = null; 161 | if(!isset($this->config['phpagi']['tempdir'])) $this->config['phpagi']['tempdir'] = AST_TMP_DIR; 162 | 163 | // festival TTS config 164 | if(!isset($this->config['festival']['text2wave'])) $this->config['festival']['text2wave'] = $this->which('text2wave'); 165 | 166 | // swift TTS config 167 | if(!isset($this->config['cepstral']['swift'])) $this->config['cepstral']['swift'] = $this->which('swift'); 168 | 169 | ob_implicit_flush(true); 170 | 171 | // open stdin & stdout 172 | $this->in = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); 173 | $this->out = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); 174 | 175 | // initialize error handler 176 | if($this->config['phpagi']['error_handler'] == true) 177 | { 178 | set_error_handler('phpagi_error_handler'); 179 | global $phpagi_error_handler_email; 180 | $phpagi_error_handler_email = $this->config['phpagi']['admin']; 181 | error_reporting(E_ALL); 182 | } 183 | 184 | // make sure temp folder exists 185 | $this->make_folder($this->config['phpagi']['tempdir']); 186 | 187 | // read the request 188 | $str = fgets($this->in); 189 | while($str != "\n") 190 | { 191 | $this->request[substr($str, 0, strpos($str, ':'))] = trim(substr($str, strpos($str, ':') + 1)); 192 | $str = fgets($this->in); 193 | } 194 | 195 | // open audio if eagi detected 196 | if($this->request['agi_enhanced'] == '1.0') 197 | { 198 | if(file_exists('/proc/' . getmypid() . '/fd/3')) 199 | $this->audio = fopen('/proc/' . getmypid() . '/fd/3', 'r'); 200 | elseif(file_exists('/dev/fd/3')) 201 | { 202 | // may need to mount fdescfs 203 | $this->audio = fopen('/dev/fd/3', 'r'); 204 | } 205 | else 206 | $this->conlog('Unable to open audio stream'); 207 | 208 | if($this->audio) stream_set_blocking($this->audio, 0); 209 | } 210 | 211 | $this->conlog('AGI Request:'); 212 | $this->conlog(print_r($this->request, true)); 213 | $this->conlog('PHPAGI internal configuration:'); 214 | $this->conlog(print_r($this->config, true)); 215 | } 216 | 217 | // ********************************************************************************************************* 218 | // ** COMMANDS ** 219 | // ********************************************************************************************************* 220 | 221 | /** 222 | * Answer channel if not already in answer state. 223 | * 224 | * @link http://www.voip-info.org/wiki-answer 225 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 226 | * @example examples/input.php Get text input from the user and say it back 227 | * @example examples/ping.php Ping an IP address 228 | * 229 | * @return array, see evaluate for return information. ['result'] is 0 on success, -1 on failure. 230 | */ 231 | function answer() 232 | { 233 | return $this->evaluate('ANSWER'); 234 | } 235 | 236 | /** 237 | * Get the status of the specified channel. If no channel name is specified, return the status of the current channel. 238 | * 239 | * @link http://www.voip-info.org/wiki-channel+status 240 | * @param string $channel 241 | * @return array, see evaluate for return information. ['data'] contains description. 242 | */ 243 | function channel_status($channel='') 244 | { 245 | $ret = $this->evaluate("CHANNEL STATUS $channel"); 246 | switch($ret['result']) 247 | { 248 | case -1: $ret['data'] = trim("There is no channel that matches $channel"); break; 249 | case AST_STATE_DOWN: $ret['data'] = 'Channel is down and available'; break; 250 | case AST_STATE_RESERVED: $ret['data'] = 'Channel is down, but reserved'; break; 251 | case AST_STATE_OFFHOOK: $ret['data'] = 'Channel is off hook'; break; 252 | case AST_STATE_DIALING: $ret['data'] = 'Digits (or equivalent) have been dialed'; break; 253 | case AST_STATE_RING: $ret['data'] = 'Line is ringing'; break; 254 | case AST_STATE_RINGING: $ret['data'] = 'Remote end is ringing'; break; 255 | case AST_STATE_UP: $ret['data'] = 'Line is up'; break; 256 | case AST_STATE_BUSY: $ret['data'] = 'Line is busy'; break; 257 | case AST_STATE_DIALING_OFFHOOK: $ret['data'] = 'Digits (or equivalent) have been dialed while offhook'; break; 258 | case AST_STATE_PRERING: $ret['data'] = 'Channel has detected an incoming call and is waiting for ring'; break; 259 | default: $ret['data'] = "Unknown ({$ret['result']})"; break; 260 | } 261 | return $ret; 262 | } 263 | 264 | /** 265 | * Deletes an entry in the Asterisk database for a given family and key. 266 | * 267 | * @link http://www.voip-info.org/wiki-database+del 268 | * @param string $family 269 | * @param string $key 270 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. 271 | */ 272 | function database_del($family, $key) 273 | { 274 | return $this->evaluate("DATABASE DEL \"$family\" \"$key\""); 275 | } 276 | 277 | /** 278 | * Deletes a family or specific keytree within a family in the Asterisk database. 279 | * 280 | * @link http://www.voip-info.org/wiki-database+deltree 281 | * @param string $family 282 | * @param string $keytree 283 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. 284 | */ 285 | function database_deltree($family, $keytree='') 286 | { 287 | $cmd = "DATABASE DELTREE \"$family\""; 288 | if($keytree != '') $cmd .= " \"$keytree\""; 289 | return $this->evaluate($cmd); 290 | } 291 | 292 | /** 293 | * Retrieves an entry in the Asterisk database for a given family and key. 294 | * 295 | * @link http://www.voip-info.org/wiki-database+get 296 | * @param string $family 297 | * @param string $key 298 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 failure. ['data'] holds the value 299 | */ 300 | function database_get($family, $key) 301 | { 302 | return $this->evaluate("DATABASE GET \"$family\" \"$key\""); 303 | } 304 | 305 | /** 306 | * Adds or updates an entry in the Asterisk database for a given family, key, and value. 307 | * 308 | * @param string $family 309 | * @param string $key 310 | * @param string $value 311 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise 312 | */ 313 | function database_put($family, $key, $value) 314 | { 315 | $value = str_replace("\n", '\n', addslashes($value)); 316 | return $this->evaluate("DATABASE PUT \"$family\" \"$key\" \"$value\""); 317 | } 318 | 319 | 320 | /** 321 | * Sets a global variable, using Asterisk 1.6 syntax. 322 | * 323 | * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set 324 | * 325 | * @param string $pVariable 326 | * @param string|int|float $pValue 327 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise 328 | */ 329 | function set_global_var($pVariable, $pValue) 330 | { 331 | if (is_numeric($pValue)) 332 | return $this->evaluate("Set({$pVariable}={$pValue},g);"); 333 | else 334 | return $this->evaluate("Set({$pVariable}=\"{$pValue}\",g);"); 335 | } 336 | 337 | 338 | /** 339 | * Sets a variable, using Asterisk 1.6 syntax. 340 | * 341 | * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set 342 | * 343 | * @param string $pVariable 344 | * @param string|int|float $pValue 345 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise 346 | */ 347 | function set_var($pVariable, $pValue) 348 | { 349 | if (is_numeric($pValue)) 350 | return $this->evaluate("Set({$pVariable}={$pValue});"); 351 | else 352 | return $this->evaluate("Set({$pVariable}=\"{$pValue}\");"); 353 | } 354 | 355 | 356 | /** 357 | * Executes the specified Asterisk application with given options. 358 | * 359 | * @link http://www.voip-info.org/wiki-exec 360 | * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands 361 | * @param string $application 362 | * @param mixed $options 363 | * @return array, see evaluate for return information. ['result'] is whatever the application returns, or -2 on failure to find application 364 | */ 365 | function exec($application, $options) 366 | { 367 | if(is_array($options)) $options = join('|', $options); 368 | return $this->evaluate("EXEC $application $options"); 369 | } 370 | 371 | /** 372 | * Plays the given file and receives DTMF data. 373 | * 374 | * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, 375 | * while STREAM FILE returns immediately after the first DTMF digit is detected. 376 | * 377 | * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. 378 | * 379 | * If the user doesn't press any keys when the message plays, there is $timeout milliseconds 380 | * of silence then the command ends. 381 | * 382 | * The user has the opportunity to press a key at any time during the message or the 383 | * post-message silence. If the user presses a key while the message is playing, the 384 | * message stops playing. When the first key is pressed a timer starts counting for 385 | * $timeout milliseconds. Every time the user presses another key the timer is restarted. 386 | * The command ends when the counter goes to zero or the maximum number of digits is entered, 387 | * whichever happens first. 388 | * 389 | * If you don't specify a time out then a default timeout of 2000 is used following a pressed 390 | * digit. If no digits are pressed then 6 seconds of silence follow the message. 391 | * 392 | * If you don't specify $max_digits then the user can enter as many digits as they want. 393 | * 394 | * Pressing the # key has the same effect as the timer running out: the command ends and 395 | * any previously keyed digits are returned. A side effect of this is that there is no 396 | * way to read a # key using this command. 397 | * 398 | * @example examples/ping.php Ping an IP address 399 | * 400 | * @link http://www.voip-info.org/wiki-get+data 401 | * @param string $filename file to play. Do not include file extension. 402 | * @param integer $timeout milliseconds 403 | * @param integer $max_digits 404 | * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. 405 | * 406 | * This differs from other commands with return DTMF as numbers representing ASCII characters. 407 | */ 408 | function get_data($filename, $timeout=null, $max_digits=null) 409 | { 410 | return $this->evaluate(rtrim("GET DATA $filename $timeout $max_digits")); 411 | } 412 | 413 | /** 414 | * Fetch the value of a variable. 415 | * 416 | * Does not work with global variables. Does not work with some variables that are generated by modules. 417 | * 418 | * @link http://www.voip-info.org/wiki-get+variable 419 | * @link http://www.voip-info.org/wiki-Asterisk+variables 420 | * @param string $variable name 421 | * @param boolean $getvalue return the value only 422 | * @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 423 | */ 424 | function get_variable($variable,$getvalue=false) 425 | { 426 | $res=$this->evaluate("GET VARIABLE $variable"); 427 | 428 | if($getvalue==false) 429 | return($res); 430 | 431 | return($res['data']); 432 | } 433 | 434 | 435 | /** 436 | * Fetch the value of a full variable. 437 | * 438 | * 439 | * @link http://www.voip-info.org/wiki/view/get+full+variable 440 | * @link http://www.voip-info.org/wiki-Asterisk+variables 441 | * @param string $variable name 442 | * @param string $channel channel 443 | * @param boolean $getvalue return the value only 444 | * @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 445 | */ 446 | function get_fullvariable($variable,$channel=false,$getvalue=false) 447 | { 448 | if($channel==false){ 449 | $req = $variable; 450 | } else { 451 | $req = $variable.' '.$channel; 452 | } 453 | 454 | $res=$this->evaluate('GET FULL VARIABLE '.$req); 455 | 456 | if($getvalue==false) 457 | return($res); 458 | 459 | return($res['data']); 460 | 461 | } 462 | 463 | /** 464 | * Hangup the specified channel. If no channel name is given, hang up the current channel. 465 | * 466 | * With power comes responsibility. Hanging up channels other than your own isn't something 467 | * that is done routinely. If you are not sure why you are doing so, then don't. 468 | * 469 | * @link http://www.voip-info.org/wiki-hangup 470 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 471 | * @example examples/input.php Get text input from the user and say it back 472 | * @example examples/ping.php Ping an IP address 473 | * 474 | * @param string $channel 475 | * @return array, see evaluate for return information. ['result'] is 1 on success, -1 on failure. 476 | */ 477 | function hangup($channel='') 478 | { 479 | return $this->evaluate("HANGUP $channel"); 480 | } 481 | 482 | /** 483 | * Does nothing. 484 | * 485 | * @link http://www.voip-info.org/wiki-noop 486 | * @return array, see evaluate for return information. 487 | */ 488 | function noop($string="") 489 | { 490 | return $this->evaluate("NOOP \"$string\""); 491 | } 492 | 493 | /** 494 | * Receive a character of text from a connected channel. Waits up to $timeout milliseconds for 495 | * a character to arrive, or infinitely if $timeout is zero. 496 | * 497 | * @link http://www.voip-info.org/wiki-receive+char 498 | * @param integer $timeout milliseconds 499 | * @return array, see evaluate for return information. ['result'] is 0 on timeout or not supported, -1 on failure. Otherwise 500 | * it is the decimal value of the DTMF tone. Use chr() to convert to ASCII. 501 | */ 502 | function receive_char($timeout=-1) 503 | { 504 | return $this->evaluate("RECEIVE CHAR $timeout"); 505 | } 506 | 507 | /** 508 | * Record sound to a file until an acceptable DTMF digit is received or a specified amount of 509 | * time has passed. Optionally the file BEEP is played before recording begins. 510 | * 511 | * @link http://www.voip-info.org/wiki-record+file 512 | * @param string $file to record, without extension, often created in /var/lib/asterisk/sounds 513 | * @param string $format of the file. GSM and WAV are commonly used formats. MP3 is read-only and thus cannot be used. 514 | * @param string $escape_digits 515 | * @param integer $timeout is the maximum record time in milliseconds, or -1 for no timeout. 516 | * @param integer $offset to seek to without exceeding the end of the file. 517 | * @param boolean $beep 518 | * @param integer $silence number of seconds of silence allowed before the function returns despite the 519 | * lack of dtmf digits or reaching timeout. 520 | * @return array, see evaluate for return information. ['result'] is -1 on error, 0 on hangup, otherwise a decimal value of the 521 | * DTMF tone. Use chr() to convert to ASCII. 522 | */ 523 | function record_file($file, $format, $escape_digits='', $timeout=-1, $offset=null, $beep=false, $silence=null) 524 | { 525 | $cmd = trim("RECORD FILE $file $format \"$escape_digits\" $timeout $offset"); 526 | if($beep) $cmd .= ' BEEP'; 527 | if(!is_null($silence)) $cmd .= " s=$silence"; 528 | return $this->evaluate($cmd); 529 | } 530 | 531 | /** 532 | * Say a given character string, returning early if any of the given DTMF digits are received on the channel. 533 | * 534 | * @link https://www.voip-info.org/say-alpha 535 | * @param string $text 536 | * @param string $escape_digits 537 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 538 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 539 | */ 540 | function say_alpha($text, $escape_digits='') 541 | { 542 | return $this->evaluate("SAY ALPHA $text \"$escape_digits\""); 543 | } 544 | /** 545 | * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. 546 | * 547 | * @link http://www.voip-info.org/wiki-say+digits 548 | * @param integer $digits 549 | * @param string $escape_digits 550 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 551 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 552 | */ 553 | function say_digits($digits, $escape_digits='') 554 | { 555 | return $this->evaluate("SAY DIGITS $digits \"$escape_digits\""); 556 | } 557 | 558 | /** 559 | * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. 560 | * 561 | * @link http://www.voip-info.org/wiki-say+number 562 | * @param integer $number 563 | * @param string $escape_digits 564 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 565 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 566 | */ 567 | function say_number($number, $escape_digits='') 568 | { 569 | return $this->evaluate("SAY NUMBER $number \"$escape_digits\""); 570 | } 571 | 572 | /** 573 | * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. 574 | * 575 | * @link http://www.voip-info.org/wiki-say+phonetic 576 | * @param string $text 577 | * @param string $escape_digits 578 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 579 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 580 | */ 581 | function say_phonetic($text, $escape_digits='') 582 | { 583 | return $this->evaluate("SAY PHONETIC $text \"$escape_digits\""); 584 | } 585 | 586 | /** 587 | * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. 588 | * 589 | * @link http://www.voip-info.org/wiki-say+time 590 | * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). 591 | * @param string $escape_digits 592 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 593 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 594 | */ 595 | function say_time($time=null, $escape_digits='') 596 | { 597 | if(is_null($time)) $time = time(); 598 | return $this->evaluate("SAY TIME $time \"$escape_digits\""); 599 | } 600 | 601 | /** 602 | * Send the specified image on a channel. 603 | * 604 | * Most channels do not support the transmission of images. 605 | * 606 | * @link http://www.voip-info.org/wiki-send+image 607 | * @param string $image without extension, often in /var/lib/asterisk/images 608 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the image is sent or 609 | * channel does not support image transmission. 610 | */ 611 | function send_image($image) 612 | { 613 | return $this->evaluate("SEND IMAGE $image"); 614 | } 615 | 616 | /** 617 | * Send the given text to the connected channel. 618 | * 619 | * Most channels do not support transmission of text. 620 | * 621 | * @link http://www.voip-info.org/wiki-send+text 622 | * @param $text 623 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the text is sent or 624 | * channel does not support text transmission. 625 | */ 626 | function send_text($text) 627 | { 628 | return $this->evaluate("SEND TEXT \"$text\""); 629 | } 630 | 631 | /** 632 | * Cause the channel to automatically hangup at $time seconds in the future. 633 | * If $time is 0 then the autohangup feature is disabled on this channel. 634 | * 635 | * If the channel is hungup prior to $time seconds, this setting has no effect. 636 | * 637 | * @link http://www.voip-info.org/wiki-set+autohangup 638 | * @param integer $time until automatic hangup 639 | * @return array, see evaluate for return information. 640 | */ 641 | function set_autohangup($time=0) 642 | { 643 | return $this->evaluate("SET AUTOHANGUP $time"); 644 | } 645 | 646 | /** 647 | * Changes the caller ID of the current channel. 648 | * 649 | * @link http://www.voip-info.org/wiki-set+callerid 650 | * @param string $cid example: "John Smith"<1234567> 651 | * This command will let you take liberties with the but the format shown in the example above works 652 | * well: the name enclosed in double quotes followed immediately by the number inside angle brackets. If there is no name then 653 | * you can omit it. If the name contains no spaces you can omit the double quotes around it. The number must follow the name 654 | * immediately; don't put a space between them. The angle brackets around the number are necessary; if you omit them the 655 | * number will be considered to be part of the name. 656 | * @return array, see evaluate for return information. 657 | */ 658 | function set_callerid($cid) 659 | { 660 | return $this->evaluate("SET CALLERID $cid"); 661 | } 662 | 663 | /** 664 | * Sets the context for continuation upon exiting the application. 665 | * 666 | * Setting the context does NOT automatically reset the extension and the priority; if you want to start at the top of the new 667 | * context you should set extension and priority yourself. 668 | * 669 | * If you specify a non-existent context you receive no error indication (['result'] is still 0) but you do get a 670 | * warning message on the Asterisk console. 671 | * 672 | * @link http://www.voip-info.org/wiki-set+context 673 | * @param string $context 674 | * @return array, see evaluate for return information. 675 | */ 676 | function set_context($context) 677 | { 678 | return $this->evaluate("SET CONTEXT $context"); 679 | } 680 | 681 | /** 682 | * Set the extension to be used for continuation upon exiting the application. 683 | * 684 | * Setting the extension does NOT automatically reset the priority. If you want to start with the first priority of the 685 | * extension you should set the priority yourself. 686 | * 687 | * If you specify a non-existent extension you receive no error indication (['result'] is still 0) but you do 688 | * get a warning message on the Asterisk console. 689 | * 690 | * @link http://www.voip-info.org/wiki-set+extension 691 | * @param string $extension 692 | * @return array, see evaluate for return information. 693 | */ 694 | function set_extension($extension) 695 | { 696 | return $this->evaluate("SET EXTENSION $extension"); 697 | } 698 | 699 | /** 700 | * Enable/Disable Music on hold generator. 701 | * 702 | * @link http://www.voip-info.org/wiki-set+music 703 | * @param boolean $enabled 704 | * @param string $class 705 | * @return array, see evaluate for return information. 706 | */ 707 | function set_music($enabled=true, $class='') 708 | { 709 | $enabled = ($enabled) ? 'ON' : 'OFF'; 710 | return $this->evaluate("SET MUSIC $enabled $class"); 711 | } 712 | 713 | /** 714 | * Set the priority to be used for continuation upon exiting the application. 715 | * 716 | * If you specify a non-existent priority you receive no error indication (['result'] is still 0) 717 | * and no warning is issued on the Asterisk console. 718 | * 719 | * @link http://www.voip-info.org/wiki-set+priority 720 | * @param integer $priority 721 | * @return array, see evaluate for return information. 722 | */ 723 | function set_priority($priority) 724 | { 725 | return $this->evaluate("SET PRIORITY $priority"); 726 | } 727 | 728 | /** 729 | * Sets a variable to the specified value. The variables so created can later be used by later using ${} 730 | * in the dialplan. 731 | * 732 | * These variables live in the channel Asterisk creates when you pickup a phone and as such they are both local and temporary. 733 | * Variables created in one channel can not be accessed by another channel. When you hang up the phone, the channel is deleted 734 | * and any variables in that channel are deleted as well. 735 | * 736 | * @link http://www.voip-info.org/wiki-set+variable 737 | * @param string $variable is case sensitive 738 | * @param string $value 739 | * @return array, see evaluate for return information. 740 | */ 741 | function set_variable($variable, $value) 742 | { 743 | $value = str_replace("\n", '\n', addslashes($value)); 744 | return $this->evaluate("SET VARIABLE $variable \"$value\""); 745 | } 746 | 747 | /** 748 | * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA 749 | * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of 750 | * digits before returning. 751 | * 752 | * @example examples/ping.php Ping an IP address 753 | * 754 | * @link http://www.voip-info.org/wiki-stream+file 755 | * @param string $filename without extension, often in /var/lib/asterisk/sounds 756 | * @param string $escape_digits 757 | * @param integer $offset 758 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 759 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 760 | */ 761 | function stream_file($filename, $escape_digits='', $offset=0) 762 | { 763 | return $this->evaluate("STREAM FILE $filename \"$escape_digits\" $offset"); 764 | } 765 | 766 | /** 767 | * Enable or disable TDD transmission/reception on the current channel. 768 | * 769 | * @link http://www.voip-info.org/wiki-tdd+mode 770 | * @param string $setting can be on, off or mate 771 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 if the channel is not TDD capable. 772 | */ 773 | function tdd_mode($setting) 774 | { 775 | return $this->evaluate("TDD MODE $setting"); 776 | } 777 | 778 | /** 779 | * Sends $message to the Asterisk console via the 'verbose' message system. 780 | * 781 | * If the Asterisk verbosity level is $level or greater, send $message to the console. 782 | * 783 | * The Asterisk verbosity system works as follows. The Asterisk user gets to set the desired verbosity at startup time or later 784 | * using the console 'set verbose' command. Messages are displayed on the console if their verbose level is less than or equal 785 | * to desired verbosity set by the user. More important messages should have a low verbose level; less important messages 786 | * should have a high verbose level. 787 | * 788 | * @link http://www.voip-info.org/wiki-verbose 789 | * @param string $message 790 | * @param integer $level from 1 to 4 791 | * @return array, see evaluate for return information. 792 | */ 793 | function verbose($message, $level=1) 794 | { 795 | foreach(explode("\n", str_replace("\r\n", "\n", print_r($message, true))) as $msg) 796 | { 797 | @syslog(LOG_WARNING, $msg); 798 | $ret = $this->evaluate("VERBOSE \"$msg\" $level"); 799 | } 800 | return $ret; 801 | } 802 | 803 | /** 804 | * Waits up to $timeout milliseconds for channel to receive a DTMF digit. 805 | * 806 | * @link http://www.voip-info.org/wiki-wait+for+digit 807 | * @param integer $timeout in millisecons. Use -1 for the timeout value if you want the call to wait indefinitely. 808 | * @return array, see evaluate for return information. ['result'] is 0 if wait completes with no 809 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 810 | */ 811 | function wait_for_digit($timeout=-1) 812 | { 813 | return $this->evaluate("WAIT FOR DIGIT $timeout"); 814 | } 815 | 816 | 817 | // ********************************************************************************************************* 818 | // ** APPLICATIONS ** 819 | // ********************************************************************************************************* 820 | 821 | /** 822 | * Set absolute maximum time of call. 823 | * 824 | * Note that the timeout is set from the current time forward, not counting the number of seconds the call has already been up. 825 | * Each time you call AbsoluteTimeout(), all previous absolute timeouts are cancelled. 826 | * Will return the call to the T extension so that you can playback an explanatory note to the calling party (the called party 827 | * will not hear that) 828 | * 829 | * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands 830 | * @link http://www.dynx.net/ASTERISK/AGI/ccard/agi-ccard.agi 831 | * @param $seconds allowed, 0 disables timeout 832 | * @return array, see evaluate for return information. 833 | */ 834 | function exec_absolutetimeout($seconds=0) 835 | { 836 | return $this->exec('AbsoluteTimeout', $seconds); 837 | } 838 | 839 | /** 840 | * Executes an AGI compliant application. 841 | * 842 | * @param string $command 843 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or if application requested hangup, or 0 on non-hangup exit. 844 | * @param string $args 845 | */ 846 | function exec_agi($command, $args) 847 | { 848 | return $this->exec("AGI $command", $args); 849 | } 850 | 851 | /** 852 | * Set Language. 853 | * 854 | * @param string $language code 855 | * @return array, see evaluate for return information. 856 | */ 857 | function exec_setlanguage($language='en') 858 | { 859 | return $this->exec('Set', 'CHANNEL(language)='. $language); 860 | } 861 | 862 | /** 863 | * Do ENUM Lookup. 864 | * 865 | * Note: to retrieve the result, use 866 | * get_variable('ENUM'); 867 | * 868 | * @param $exten 869 | * @return array, see evaluate for return information. 870 | */ 871 | function exec_enumlookup($exten) 872 | { 873 | return $this->exec('EnumLookup', $exten); 874 | } 875 | 876 | /** 877 | * Dial. 878 | * 879 | * Dial takes input from ${VXML_URL} to send XML Url to Cisco 7960 880 | * Dial takes input from ${ALERT_INFO} to set ring cadence for Cisco phones 881 | * Dial returns ${CAUSECODE}: If the dial failed, this is the errormessage. 882 | * Dial returns ${DIALSTATUS}: Text code returning status of last dial attempt. 883 | * 884 | * @link http://www.voip-info.org/wiki-Asterisk+cmd+Dial 885 | * @param string $type 886 | * @param string $identifier 887 | * @param integer $timeout 888 | * @param string $options 889 | * @param string $url 890 | * @return array, see evaluate for return information. 891 | */ 892 | function exec_dial($type, $identifier, $timeout=null, $options=null, $url=null) 893 | { 894 | return $this->exec('Dial', trim("$type/$identifier".$this->option_delim.$timeout.$this->option_delim.$options.$this->option_delim.$url, $this->option_delim)); 895 | } 896 | 897 | /** 898 | * Goto. 899 | * 900 | * This function takes three arguments: context,extension, and priority, but the leading arguments 901 | * are optional, not the trailing arguments. Thuse goto($z) sets the priority to $z. 902 | * 903 | * @param string $a 904 | * @param string $b; 905 | * @param string $c; 906 | * @return array, see evaluate for return information. 907 | */ 908 | function exec_goto($a, $b=null, $c=null) 909 | { 910 | return $this->exec('Goto', trim($a.$this->option_delim.$b.$this->option_delim.$c, $this->option_delim)); 911 | } 912 | 913 | 914 | // ********************************************************************************************************* 915 | // ** FAST PASSING ** 916 | // ********************************************************************************************************* 917 | 918 | /** 919 | * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. 920 | * Return early if $buffer is adequate for request. 921 | * 922 | * @link http://www.voip-info.org/wiki-say+digits 923 | * @param string $buffer 924 | * @param integer $digits 925 | * @param string $escape_digits 926 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 927 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 928 | */ 929 | function fastpass_say_digits(&$buffer, $digits, $escape_digits='') 930 | { 931 | $proceed = false; 932 | if($escape_digits != '' && $buffer != '') 933 | { 934 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 935 | $proceed = true; 936 | } 937 | if($buffer == '' || $proceed) 938 | { 939 | $res = $this->say_digits($digits, $escape_digits); 940 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 941 | $buffer .= chr($res['result']); 942 | return $res; 943 | } 944 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); 945 | } 946 | 947 | /** 948 | * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. 949 | * Return early if $buffer is adequate for request. 950 | * 951 | * @link http://www.voip-info.org/wiki-say+number 952 | * @param string $buffer 953 | * @param integer $number 954 | * @param string $escape_digits 955 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 956 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 957 | */ 958 | function fastpass_say_number(&$buffer, $number, $escape_digits='') 959 | { 960 | $proceed = false; 961 | if($escape_digits != '' && $buffer != '') 962 | { 963 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 964 | $proceed = true; 965 | } 966 | if($buffer == '' || $proceed) 967 | { 968 | $res = $this->say_number($number, $escape_digits); 969 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 970 | $buffer .= chr($res['result']); 971 | return $res; 972 | } 973 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); 974 | } 975 | 976 | /** 977 | * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. 978 | * Return early if $buffer is adequate for request. 979 | * 980 | * @link http://www.voip-info.org/wiki-say+phonetic 981 | * @param string $buffer 982 | * @param string $text 983 | * @param string $escape_digits 984 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 985 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 986 | */ 987 | function fastpass_say_phonetic(&$buffer, $text, $escape_digits='') 988 | { 989 | $proceed = false; 990 | if($escape_digits != '' && $buffer != '') 991 | { 992 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 993 | $proceed = true; 994 | } 995 | if($buffer == '' || $proceed) 996 | { 997 | $res = $this->say_phonetic($text, $escape_digits); 998 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 999 | $buffer .= chr($res['result']); 1000 | return $res; 1001 | } 1002 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); 1003 | } 1004 | 1005 | /** 1006 | * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. 1007 | * Return early if $buffer is adequate for request. 1008 | * 1009 | * @link http://www.voip-info.org/wiki-say+time 1010 | * @param string $buffer 1011 | * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). 1012 | * @param string $escape_digits 1013 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 1014 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 1015 | */ 1016 | function fastpass_say_time(&$buffer, $time=null, $escape_digits='') 1017 | { 1018 | $proceed = false; 1019 | if($escape_digits != '' && $buffer != '') 1020 | { 1021 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 1022 | $proceed = true; 1023 | } 1024 | if($buffer == '' || $proceed) 1025 | { 1026 | $res = $this->say_time($time, $escape_digits); 1027 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 1028 | $buffer .= chr($res['result']); 1029 | return $res; 1030 | } 1031 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); 1032 | } 1033 | 1034 | /** 1035 | * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA 1036 | * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of 1037 | * digits before returning. 1038 | * Return early if $buffer is adequate for request. 1039 | * 1040 | * @link http://www.voip-info.org/wiki-stream+file 1041 | * @param string $buffer 1042 | * @param string $filename without extension, often in /var/lib/asterisk/sounds 1043 | * @param string $escape_digits 1044 | * @param integer $offset 1045 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 1046 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 1047 | */ 1048 | function fastpass_stream_file(&$buffer, $filename, $escape_digits='', $offset=0) 1049 | { 1050 | $proceed = false; 1051 | if($escape_digits != '' && $buffer != '') 1052 | { 1053 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 1054 | $proceed = true; 1055 | } 1056 | if($buffer == '' || $proceed) 1057 | { 1058 | $res = $this->stream_file($filename, $escape_digits, $offset); 1059 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 1060 | $buffer .= chr($res['result']); 1061 | return $res; 1062 | } 1063 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); 1064 | } 1065 | 1066 | /** 1067 | * Use festival to read text. 1068 | * Return early if $buffer is adequate for request. 1069 | * 1070 | * @link http://www.cstr.ed.ac.uk/projects/festival/ 1071 | * @param string $buffer 1072 | * @param string $text 1073 | * @param string $escape_digits 1074 | * @param integer $frequency 1075 | * @return array, see evaluate for return information. 1076 | */ 1077 | function fastpass_text2wav(&$buffer, $text, $escape_digits='', $frequency=8000) 1078 | { 1079 | $proceed = false; 1080 | if($escape_digits != '' && $buffer != '') 1081 | { 1082 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 1083 | $proceed = true; 1084 | } 1085 | if($buffer == '' || $proceed) 1086 | { 1087 | $res = $this->text2wav($text, $escape_digits, $frequency); 1088 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 1089 | $buffer .= chr($res['result']); 1090 | return $res; 1091 | } 1092 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); 1093 | } 1094 | 1095 | /** 1096 | * Use Cepstral Swift to read text. 1097 | * Return early if $buffer is adequate for request. 1098 | * 1099 | * @link http://www.cepstral.com/ 1100 | * @param string $buffer 1101 | * @param string $text 1102 | * @param string $escape_digits 1103 | * @param integer $frequency 1104 | * @return array, see evaluate for return information. 1105 | */ 1106 | function fastpass_swift(&$buffer, $text, $escape_digits='', $frequency=8000, $voice=null) 1107 | { 1108 | $proceed = false; 1109 | if($escape_digits != '' && $buffer != '') 1110 | { 1111 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 1112 | $proceed = true; 1113 | } 1114 | if($buffer == '' || $proceed) 1115 | { 1116 | $res = $this->swift($text, $escape_digits, $frequency, $voice); 1117 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 1118 | $buffer .= chr($res['result']); 1119 | return $res; 1120 | } 1121 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1]), 'endpos'=>0); 1122 | } 1123 | 1124 | /** 1125 | * Say Puncutation in a string. 1126 | * Return early if $buffer is adequate for request. 1127 | * 1128 | * @param string $buffer 1129 | * @param string $text 1130 | * @param string $escape_digits 1131 | * @param integer $frequency 1132 | * @return array, see evaluate for return information. 1133 | */ 1134 | function fastpass_say_punctuation(&$buffer, $text, $escape_digits='', $frequency=8000) 1135 | { 1136 | $proceed = false; 1137 | if($escape_digits != '' && $buffer != '') 1138 | { 1139 | if(!strpos(chr(255) . $escape_digits, $buffer[strlen($buffer)-1])) 1140 | $proceed = true; 1141 | } 1142 | if($buffer == '' || $proceed) 1143 | { 1144 | $res = $this->say_punctuation($text, $escape_digits, $frequency); 1145 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 1146 | $buffer .= chr($res['result']); 1147 | return $res; 1148 | } 1149 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer[strlen($buffer)-1])); 1150 | } 1151 | 1152 | /** 1153 | * Plays the given file and receives DTMF data. 1154 | * Return early if $buffer is adequate for request. 1155 | * 1156 | * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, 1157 | * while STREAM FILE returns immediately after the first DTMF digit is detected. 1158 | * 1159 | * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. 1160 | * 1161 | * If the user doesn't press any keys when the message plays, there is $timeout milliseconds 1162 | * of silence then the command ends. 1163 | * 1164 | * The user has the opportunity to press a key at any time during the message or the 1165 | * post-message silence. If the user presses a key while the message is playing, the 1166 | * message stops playing. When the first key is pressed a timer starts counting for 1167 | * $timeout milliseconds. Every time the user presses another key the timer is restarted. 1168 | * The command ends when the counter goes to zero or the maximum number of digits is entered, 1169 | * whichever happens first. 1170 | * 1171 | * If you don't specify a time out then a default timeout of 2000 is used following a pressed 1172 | * digit. If no digits are pressed then 6 seconds of silence follow the message. 1173 | * 1174 | * If you don't specify $max_digits then the user can enter as many digits as they want. 1175 | * 1176 | * Pressing the # key has the same effect as the timer running out: the command ends and 1177 | * any previously keyed digits are returned. A side effect of this is that there is no 1178 | * way to read a # key using this command. 1179 | * 1180 | * @link http://www.voip-info.org/wiki-get+data 1181 | * @param string $buffer 1182 | * @param string $filename file to play. Do not include file extension. 1183 | * @param integer $timeout milliseconds 1184 | * @param integer $max_digits 1185 | * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. 1186 | * 1187 | * This differs from other commands with return DTMF as numbers representing ASCII characters. 1188 | */ 1189 | function fastpass_get_data(&$buffer, $filename, $timeout=null, $max_digits=null) 1190 | { 1191 | if(is_null($max_digits) || strlen($buffer) < $max_digits) 1192 | { 1193 | if($buffer == '') 1194 | { 1195 | $res = $this->get_data($filename, $timeout, $max_digits); 1196 | if($res['code'] == AGIRES_OK) 1197 | $buffer .= $res['result']; 1198 | return $res; 1199 | } 1200 | else 1201 | { 1202 | while(is_null($max_digits) || strlen($buffer) < $max_digits) 1203 | { 1204 | $res = $this->wait_for_digit(); 1205 | if($res['code'] != AGIRES_OK) return $res; 1206 | if($res['result'] == ord('#')) break; 1207 | $buffer .= chr($res['result']); 1208 | } 1209 | } 1210 | } 1211 | return array('code'=>AGIRES_OK, 'result'=>$buffer); 1212 | } 1213 | 1214 | // ********************************************************************************************************* 1215 | // ** DERIVED ** 1216 | // ********************************************************************************************************* 1217 | 1218 | /** 1219 | * Menu. 1220 | * 1221 | * This function presents the user with a menu and reads the response 1222 | * 1223 | * @param array $choices has the following structure: 1224 | * array('1'=>'*Press 1 for this', // festival reads if prompt starts with * 1225 | * '2'=>'some-gsm-without-extension', 1226 | * '*'=>'*Press star for help'); 1227 | * @return mixed key pressed on sucess, -1 on failure 1228 | */ 1229 | function menu($choices, $timeout=2000) 1230 | { 1231 | $keys = join('', array_keys($choices)); 1232 | $choice = null; 1233 | while(is_null($choice)) 1234 | { 1235 | foreach($choices as $prompt) 1236 | { 1237 | if($prompt[0] == '*') 1238 | $ret = $this->text2wav(substr($prompt, 1), $keys); 1239 | else 1240 | $ret = $this->stream_file($prompt, $keys); 1241 | 1242 | if($ret['code'] != AGIRES_OK || $ret['result'] == -1) 1243 | { 1244 | $choice = -1; 1245 | break; 1246 | } 1247 | 1248 | if($ret['result'] != 0) 1249 | { 1250 | $choice = chr($ret['result']); 1251 | break; 1252 | } 1253 | } 1254 | 1255 | if(is_null($choice)) 1256 | { 1257 | $ret = $this->get_data('beep', $timeout, 1); 1258 | if($ret['code'] != AGIRES_OK || $ret['result'] == -1) 1259 | $choice = -1; 1260 | elseif($ret['result'] != '' && strpos(' '.$keys, $ret['result'])) 1261 | $choice = $ret['result']; 1262 | } 1263 | } 1264 | return $choice; 1265 | } 1266 | 1267 | /** 1268 | * setContext - Set context, extension and priority. 1269 | * 1270 | * @param string $context 1271 | * @param string $extension 1272 | * @param string $priority 1273 | */ 1274 | function setContext($context, $extension='s', $priority=1) 1275 | { 1276 | $this->set_context($context); 1277 | $this->set_extension($extension); 1278 | $this->set_priority($priority); 1279 | } 1280 | 1281 | /** 1282 | * Parse caller id. 1283 | * 1284 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 1285 | * @example examples/input.php Get text input from the user and say it back 1286 | * 1287 | * "name" 1288 | * 1289 | * @param string $callerid 1290 | * @return array('Name'=>$name, 'Number'=>$number) 1291 | */ 1292 | function parse_callerid($callerid=null) 1293 | { 1294 | if(is_null($callerid)) 1295 | $callerid = $this->request['agi_callerid']; 1296 | 1297 | $ret = array('name'=>'', 'protocol'=>'', 'username'=>'', 'host'=>'', 'port'=>''); 1298 | $callerid = trim($callerid); 1299 | 1300 | if($callerid[0] == '"' || $callerid[0] == "'") 1301 | { 1302 | $d = $callerid[0]; 1303 | $callerid = explode($d, substr($callerid, 1)); 1304 | $ret['name'] = array_shift($callerid); 1305 | $callerid = join($d, $callerid); 1306 | } 1307 | 1308 | $callerid = explode('@', trim($callerid, '<> ')); 1309 | $username = explode(':', array_shift($callerid)); 1310 | if(count($username) == 1) 1311 | $ret['username'] = $username[0]; 1312 | else 1313 | { 1314 | $ret['protocol'] = array_shift($username); 1315 | $ret['username'] = join(':', $username); 1316 | } 1317 | 1318 | $callerid = join('@', $callerid); 1319 | $host = explode(':', $callerid); 1320 | if(count($host) == 1) 1321 | $ret['host'] = $host[0]; 1322 | else 1323 | { 1324 | $ret['host'] = array_shift($host); 1325 | $ret['port'] = join(':', $host); 1326 | } 1327 | 1328 | return $ret; 1329 | } 1330 | 1331 | /** 1332 | * Use festival to read text. 1333 | * 1334 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 1335 | * @example examples/input.php Get text input from the user and say it back 1336 | * @example examples/ping.php Ping an IP address 1337 | * 1338 | * @link http://www.cstr.ed.ac.uk/projects/festival/ 1339 | * @param string $text 1340 | * @param string $escape_digits 1341 | * @param integer $frequency 1342 | * @return array, see evaluate for return information. 1343 | */ 1344 | function text2wav($text, $escape_digits='', $frequency=8000) 1345 | { 1346 | $text = trim($text); 1347 | if($text == '') return true; 1348 | 1349 | $hash = md5($text); 1350 | $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; 1351 | $fname .= 'text2wav_' . $hash; 1352 | 1353 | // create wave file 1354 | if(!file_exists("$fname.wav")) 1355 | { 1356 | // write text file 1357 | if(!file_exists("$fname.txt")) 1358 | { 1359 | $fp = fopen("$fname.txt", 'w'); 1360 | fputs($fp, $text); 1361 | fclose($fp); 1362 | } 1363 | 1364 | shell_exec("{$this->config['festival']['text2wave']} -F $frequency -o $fname.wav $fname.txt"); 1365 | } 1366 | else 1367 | { 1368 | touch("$fname.txt"); 1369 | touch("$fname.wav"); 1370 | } 1371 | 1372 | // stream it 1373 | $ret = $this->stream_file($fname, $escape_digits); 1374 | 1375 | // clean up old files 1376 | $delete = time() - 2592000; // 1 month 1377 | foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'text2wav_*') as $file) 1378 | if(filemtime($file) < $delete) 1379 | unlink($file); 1380 | 1381 | return $ret; 1382 | } 1383 | 1384 | /** 1385 | * Use Cepstral Swift to read text. 1386 | * 1387 | * @link http://www.cepstral.com/ 1388 | * @param string $text 1389 | * @param string $escape_digits 1390 | * @param integer $frequency 1391 | * @return array, see evaluate for return information. 1392 | */ 1393 | function swift($text, $escape_digits='', $frequency=8000, $voice=null) 1394 | { 1395 | if(!is_null($voice)) 1396 | $voice = "-n $voice"; 1397 | elseif(isset($this->config['cepstral']['voice'])) 1398 | $voice = "-n {$this->config['cepstral']['voice']}"; 1399 | 1400 | $text = trim($text); 1401 | if($text == '') return true; 1402 | 1403 | $hash = md5($text); 1404 | $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; 1405 | $fname .= 'swift_' . $hash; 1406 | 1407 | // create wave file 1408 | if(!file_exists("$fname.wav")) 1409 | { 1410 | // write text file 1411 | if(!file_exists("$fname.txt")) 1412 | { 1413 | $fp = fopen("$fname.txt", 'w'); 1414 | fputs($fp, $text); 1415 | fclose($fp); 1416 | } 1417 | 1418 | shell_exec("{$this->config['cepstral']['swift']} -p audio/channels=1,audio/sampling-rate=$frequency $voice -o $fname.wav -f $fname.txt"); 1419 | } 1420 | 1421 | // stream it 1422 | $ret = $this->stream_file($fname, $escape_digits); 1423 | 1424 | // clean up old files 1425 | $delete = time() - 2592000; // 1 month 1426 | foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'swift_*') as $file) 1427 | if(filemtime($file) < $delete) 1428 | unlink($file); 1429 | 1430 | return $ret; 1431 | } 1432 | 1433 | /** 1434 | * Text Input. 1435 | * 1436 | * Based on ideas found at http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText 1437 | * 1438 | * Example: 1439 | * UC H LC i , SP h o w SP a r e SP y o u ? 1440 | * $string = '*8'.'44*'.'*5'.'444*'.'00*'.'0*'.'44*'.'666*'.'9*'.'0*'.'2*'.'777*'.'33*'.'0*'.'999*'.'666*'.'88*'.'0000*'; 1441 | * 1442 | * @link http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText 1443 | * @example examples/input.php Get text input from the user and say it back 1444 | * 1445 | * @return string 1446 | */ 1447 | function text_input($mode='NUMERIC') 1448 | { 1449 | $alpha = array( 'k0'=>' ', 'k00'=>',', 'k000'=>'.', 'k0000'=>'?', 'k00000'=>'0', 1450 | 'k1'=>'!', 'k11'=>':', 'k111'=>';', 'k1111'=>'#', 'k11111'=>'1', 1451 | 'k2'=>'A', 'k22'=>'B', 'k222'=>'C', 'k2222'=>'2', 1452 | 'k3'=>'D', 'k33'=>'E', 'k333'=>'F', 'k3333'=>'3', 1453 | 'k4'=>'G', 'k44'=>'H', 'k444'=>'I', 'k4444'=>'4', 1454 | 'k5'=>'J', 'k55'=>'K', 'k555'=>'L', 'k5555'=>'5', 1455 | 'k6'=>'M', 'k66'=>'N', 'k666'=>'O', 'k6666'=>'6', 1456 | 'k7'=>'P', 'k77'=>'Q', 'k777'=>'R', 'k7777'=>'S', 'k77777'=>'7', 1457 | 'k8'=>'T', 'k88'=>'U', 'k888'=>'V', 'k8888'=>'8', 1458 | 'k9'=>'W', 'k99'=>'X', 'k999'=>'Y', 'k9999'=>'Z', 'k99999'=>'9'); 1459 | $symbol = array('k0'=>'=', 1460 | 'k1'=>'<', 'k11'=>'(', 'k111'=>'[', 'k1111'=>'{', 'k11111'=>'1', 1461 | 'k2'=>'@', 'k22'=>'$', 'k222'=>'&', 'k2222'=>'%', 'k22222'=>'2', 1462 | 'k3'=>'>', 'k33'=>')', 'k333'=>']', 'k3333'=>'}', 'k33333'=>'3', 1463 | 'k4'=>'+', 'k44'=>'-', 'k444'=>'*', 'k4444'=>'/', 'k44444'=>'4', 1464 | 'k5'=>"'", 'k55'=>'`', 'k555'=>'5', 1465 | 'k6'=>'"', 'k66'=>'6', 1466 | 'k7'=>'^', 'k77'=>'7', 1467 | 'k8'=>"\\",'k88'=>'|', 'k888'=>'8', 1468 | 'k9'=>'_', 'k99'=>'~', 'k999'=>'9'); 1469 | $text = ''; 1470 | do 1471 | { 1472 | $command = false; 1473 | $result = $this->get_data('beep'); 1474 | foreach(explode('*', $result['result']) as $code) 1475 | { 1476 | if($command) 1477 | { 1478 | switch($code[0]) 1479 | { 1480 | case '2': $text = substr($text, 0, strlen($text) - 1); break; // backspace 1481 | case '5': $mode = 'LOWERCASE'; break; 1482 | case '6': $mode = 'NUMERIC'; break; 1483 | case '7': $mode = 'SYMBOL'; break; 1484 | case '8': $mode = 'UPPERCASE'; break; 1485 | case '9': $text = explode(' ', $text); unset($text[count($text)-1]); $text = join(' ', $text); break; // backspace a word 1486 | } 1487 | $code = substr($code, 1); 1488 | $command = false; 1489 | } 1490 | if($code == '') 1491 | $command = true; 1492 | elseif($mode == 'NUMERIC') 1493 | $text .= $code; 1494 | elseif($mode == 'UPPERCASE' && isset($alpha['k'.$code])) 1495 | $text .= $alpha['k'.$code]; 1496 | elseif($mode == 'LOWERCASE' && isset($alpha['k'.$code])) 1497 | $text .= strtolower($alpha['k'.$code]); 1498 | elseif($mode == 'SYMBOL' && isset($symbol['k'.$code])) 1499 | $text .= $symbol['k'.$code]; 1500 | } 1501 | $this->say_punctuation($text); 1502 | } while(substr($result['result'], -2) == '**'); 1503 | return $text; 1504 | } 1505 | 1506 | /** 1507 | * Say Puncutation in a string. 1508 | * 1509 | * @param string $text 1510 | * @param string $escape_digits 1511 | * @param integer $frequency 1512 | * @return array, see evaluate for return information. 1513 | */ 1514 | function say_punctuation($text, $escape_digits='', $frequency=8000) 1515 | { 1516 | $ret=""; 1517 | for($i = 0; $i < strlen($text); $i++) 1518 | { 1519 | switch($text[$i]) 1520 | { 1521 | case ' ': $ret .= 'SPACE '; 1522 | case ',': $ret .= 'COMMA '; break; 1523 | case '.': $ret .= 'PERIOD '; break; 1524 | case '?': $ret .= 'QUESTION MARK '; break; 1525 | case '!': $ret .= 'EXPLANATION POINT '; break; 1526 | case ':': $ret .= 'COLON '; break; 1527 | case ';': $ret .= 'SEMICOLON '; break; 1528 | case '#': $ret .= 'POUND '; break; 1529 | case '=': $ret .= 'EQUALS '; break; 1530 | case '<': $ret .= 'LESS THAN '; break; 1531 | case '(': $ret .= 'LEFT PARENTHESIS '; break; 1532 | case '[': $ret .= 'LEFT BRACKET '; break; 1533 | case '{': $ret .= 'LEFT BRACE '; break; 1534 | case '@': $ret .= 'AT '; break; 1535 | case '$': $ret .= 'DOLLAR SIGN '; break; 1536 | case '&': $ret .= 'AMPERSAND '; break; 1537 | case '%': $ret .= 'PERCENT '; break; 1538 | case '>': $ret .= 'GREATER THAN '; break; 1539 | case ')': $ret .= 'RIGHT PARENTHESIS '; break; 1540 | case ']': $ret .= 'RIGHT BRACKET '; break; 1541 | case '}': $ret .= 'RIGHT BRACE '; break; 1542 | case '+': $ret .= 'PLUS '; break; 1543 | case '-': $ret .= 'MINUS '; break; 1544 | case '*': $ret .= 'ASTERISK '; break; 1545 | case '/': $ret .= 'SLASH '; break; 1546 | case "'": $ret .= 'SINGLE QUOTE '; break; 1547 | case '`': $ret .= 'BACK TICK '; break; 1548 | case '"': $ret .= 'QUOTE '; break; 1549 | case '^': $ret .= 'CAROT '; break; 1550 | case "\\": $ret .= 'BACK SLASH '; break; 1551 | case '|': $ret .= 'BAR '; break; 1552 | case '_': $ret .= 'UNDERSCORE '; break; 1553 | case '~': $ret .= 'TILDE '; break; 1554 | default: $ret .= $text[$i] . ' '; break; 1555 | } 1556 | } 1557 | return $this->text2wav($ret, $escape_digits, $frequency); 1558 | } 1559 | 1560 | /** 1561 | * Create a new AGI_AsteriskManager. 1562 | */ 1563 | function &new_AsteriskManager() 1564 | { 1565 | $this->asm = new AGI_AsteriskManager(null, $this->config['asmanager']); 1566 | $this->asm->setPagi($this); 1567 | $this->config['asmanager'] =& $this->asm->config['asmanager']; 1568 | return $this->asm; 1569 | } 1570 | 1571 | 1572 | // ********************************************************************************************************* 1573 | // ** PRIVATE ** 1574 | // ********************************************************************************************************* 1575 | 1576 | 1577 | /** 1578 | * Evaluate an AGI command. 1579 | * 1580 | * @access private 1581 | * @param string $command 1582 | * @return array ('code'=>$code, 'result'=>$result, 'data'=>$data) 1583 | */ 1584 | function evaluate($command) 1585 | { 1586 | $broken = array('code'=>500, 'result'=>-1, 'data'=>''); 1587 | 1588 | // write command 1589 | if(!@fwrite($this->out, trim($command) . "\n")) return $broken; 1590 | fflush($this->out); 1591 | 1592 | // Read result. Occasionally, a command return a string followed by an extra new line. 1593 | // When this happens, our script will ignore the new line, but it will still be in the 1594 | // buffer. So, if we get a blank line, it is probably the result of a previous 1595 | // command. We read until we get a valid result or asterisk hangs up. One offending 1596 | // command is SEND TEXT. 1597 | $count = 0; 1598 | do 1599 | { 1600 | $str = trim(fgets($this->in, 4096)); 1601 | } while($str == '' && $count++ < 5); 1602 | 1603 | if($count >= 5) 1604 | { 1605 | // $this->conlog("evaluate error on read for $command"); 1606 | return $broken; 1607 | } 1608 | 1609 | // parse result 1610 | $ret['code'] = substr($str, 0, 3); 1611 | $str = trim(substr($str, 3)); 1612 | 1613 | if($str[0] == '-') // we have a multiline response! 1614 | { 1615 | $count = 0; 1616 | $str = substr($str, 1) . "\n"; 1617 | $line = fgets($this->in, 4096); 1618 | while(substr($line, 0, 3) != $ret['code'] && $count < 5) 1619 | { 1620 | $str .= $line; 1621 | $line = fgets($this->in, 4096); 1622 | $count = (trim($line) == '') ? $count + 1 : 0; 1623 | } 1624 | if($count >= 5) 1625 | { 1626 | // $this->conlog("evaluate error on multiline read for $command"); 1627 | return $broken; 1628 | } 1629 | } 1630 | 1631 | $ret['result'] = null; 1632 | $ret['data'] = ''; 1633 | if($ret['code'] != AGIRES_OK) // some sort of error 1634 | { 1635 | $ret['data'] = $str; 1636 | $this->conlog(print_r($ret, true)); 1637 | } 1638 | else // normal AGIRES_OK response 1639 | { 1640 | $parse = explode(' ', trim($str)); 1641 | $in_token = false; 1642 | foreach($parse as $token) 1643 | { 1644 | if($in_token) // we previously hit a token starting with ')' but not ending in ')' 1645 | { 1646 | $ret['data'] .= ' ' . trim($token, '() '); 1647 | if($token[strlen($token)-1] == ')') $in_token = false; 1648 | } 1649 | elseif($token[0] == '(') 1650 | { 1651 | if($token[strlen($token)-1] != ')') $in_token = true; 1652 | $ret['data'] .= ' ' . trim($token, '() '); 1653 | } 1654 | elseif(strpos($token, '=')) 1655 | { 1656 | $token = explode('=', $token); 1657 | $ret[$token[0]] = $token[1]; 1658 | } 1659 | elseif($token != '') 1660 | $ret['data'] .= ' ' . $token; 1661 | } 1662 | $ret['data'] = trim($ret['data']); 1663 | } 1664 | 1665 | // log some errors 1666 | if($ret['result'] < 0) 1667 | $this->conlog("$command returned {$ret['result']}"); 1668 | 1669 | return $ret; 1670 | } 1671 | 1672 | /** 1673 | * Log to console if debug mode. 1674 | * 1675 | * @example examples/ping.php Ping an IP address 1676 | * 1677 | * @param string $str 1678 | * @param integer $vbl verbose level 1679 | */ 1680 | function conlog($str, $vbl=1) 1681 | { 1682 | static $busy = false; 1683 | 1684 | if($this->config['phpagi']['debug'] != false) 1685 | { 1686 | if(!$busy) // no conlogs inside conlog!!! 1687 | { 1688 | $busy = true; 1689 | $this->verbose($str, $vbl); 1690 | $busy = false; 1691 | } 1692 | } 1693 | } 1694 | 1695 | /** 1696 | * Find an execuable in the path. 1697 | * 1698 | * @access private 1699 | * @param string $cmd command to find 1700 | * @param string $checkpath path to check 1701 | * @return string the path to the command 1702 | */ 1703 | function which($cmd, $checkpath=null) 1704 | { 1705 | if (is_null($checkpath)) { 1706 | $chpath = getenv('PATH'); 1707 | if ($chpath === false) { 1708 | $chpath = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:'. 1709 | '/usr/X11R6/bin:/usr/local/apache/bin:/usr/local/mysql/bin'; 1710 | } 1711 | } else { 1712 | $chpath = $checkpath; 1713 | } 1714 | 1715 | foreach(explode(':', $chpath) as $path) 1716 | if(is_executable("$path/$cmd")) 1717 | return "$path/$cmd"; 1718 | 1719 | return false; 1720 | } 1721 | 1722 | /** 1723 | * Make a folder recursively. 1724 | * 1725 | * @access private 1726 | * @param string $folder 1727 | * @param integer $perms 1728 | * @return boolean 1729 | */ 1730 | function make_folder($folder, $perms=0755) 1731 | { 1732 | $f = explode(DIRECTORY_SEPARATOR, $folder); 1733 | $base = ''; 1734 | for($i = 0; $i < count($f); $i++) 1735 | { 1736 | $base .= $f[$i]; 1737 | if($f[$i] != '' && !file_exists($base)) { 1738 | if(mkdir($base, $perms)==false){ 1739 | return(false); 1740 | } 1741 | } 1742 | $base .= DIRECTORY_SEPARATOR; 1743 | } 1744 | return(true); 1745 | } 1746 | 1747 | } 1748 | 1749 | 1750 | /** 1751 | * error handler for phpagi. 1752 | * 1753 | * @param integer $level PHP error level 1754 | * @param string $message error message 1755 | * @param string $file path to file 1756 | * @param integer $line line number of error 1757 | * @param array $context variables in the current scope 1758 | */ 1759 | function phpagi_error_handler($level, $message, $file, $line, $context) 1760 | { 1761 | if(ini_get('error_reporting') == 0) return; // this happens with an @ 1762 | 1763 | @syslog(LOG_WARNING, $file . '[' . $line . ']: ' . $message); 1764 | 1765 | global $phpagi_error_handler_email; 1766 | if(function_exists('mail') && !is_null($phpagi_error_handler_email)) // generate email debugging information 1767 | { 1768 | // decode error level 1769 | switch($level) 1770 | { 1771 | case E_WARNING: 1772 | case E_USER_WARNING: 1773 | $level = "Warning"; 1774 | break; 1775 | case E_NOTICE: 1776 | case E_USER_NOTICE: 1777 | $level = "Notice"; 1778 | break; 1779 | case E_USER_ERROR: 1780 | $level = "Error"; 1781 | break; 1782 | } 1783 | 1784 | // build message 1785 | $basefile = basename($file); 1786 | $subject = "$basefile/$line/$level: $message"; 1787 | $message = "$level: $message in $file on line $line\n\n"; 1788 | 1789 | if(strpos(' '.strtolower($message), 'mysql')) { 1790 | if(function_exists('mysql_errno')) { 1791 | $message .= 'MySQL error ' . mysql_errno() . ": " . mysql_error() . "\n\n"; 1792 | } 1793 | else if(function_exists('mysqli_errno')) { 1794 | $message .= 'MySQL error ' . mysqli_errno() . ": " . mysqli_error() . "\n\n"; 1795 | } 1796 | } 1797 | 1798 | // figure out who we are 1799 | if(function_exists('socket_create')) 1800 | { 1801 | $addr = null; 1802 | $port = 80; 1803 | $socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); 1804 | @socket_connect($socket, '64.0.0.0', $port); 1805 | @socket_getsockname($socket, $addr, $port); 1806 | @socket_close($socket); 1807 | $message .= "\n\nIP Address: $addr\n"; 1808 | } 1809 | 1810 | // include variables 1811 | $message .= "\n\nContext:\n" . print_r($context, true); 1812 | $message .= "\n\nGLOBALS:\n" . print_r($GLOBALS, true); 1813 | $message .= "\n\nBacktrace:\n" . print_r(debug_backtrace(), true); 1814 | 1815 | // include code fragment 1816 | if(file_exists($file)) 1817 | { 1818 | $message .= "\n\n$file:\n"; 1819 | $code = @file($file); 1820 | for($i = max(0, $line - 10); $i < min($line + 10, count($code)); $i++) 1821 | $message .= ($i + 1)."\t$code[$i]"; 1822 | } 1823 | 1824 | // make sure message is fully readable (convert unprintable chars to hex representation) 1825 | $ret = ''; 1826 | for($i = 0; $i < strlen($message); $i++) 1827 | { 1828 | $c = ord($message[$i]); 1829 | if($c == 10 || $c == 13 || $c == 9) 1830 | $ret .= $message[$i]; 1831 | elseif($c < 16) 1832 | $ret .= '\x0' . dechex($c); 1833 | elseif($c < 32 || $c > 127) 1834 | $ret .= '\x' . dechex($c); 1835 | else 1836 | $ret .= $message[$i]; 1837 | } 1838 | $message = $ret; 1839 | 1840 | // send the mail if less than 5 errors 1841 | static $mailcount = 0; 1842 | if($mailcount < 5) 1843 | @mail($phpagi_error_handler_email, $subject, $message); 1844 | $mailcount++; 1845 | } 1846 | } 1847 | 1848 | $phpagi_error_handler_email = null; 1849 | 1850 | --------------------------------------------------------------------------------