├── LICENSE ├── README ├── agi.php ├── derived.php ├── examples ├── README ├── beep.gsm ├── dtmf.php ├── input.php ├── my_ip.php ├── ping.gsm ├── ping.php ├── ring.php ├── sip_show_peer.php ├── thanks.gsm ├── weather.php └── weather.txt.gz ├── fastpass.php ├── manager.php ├── speech.php └── utests ├── ManagerTest.php └── config.php /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | PHPAGI README 2 | $Id: README,v 1.4 2005/05/19 13:19:37 pinhole Exp $ 3 | --------------------------------------- 4 | 5 | Welcome to PHPAGI. 6 | 7 | phpagi is a set of PHP classes for use in developing applications with 8 | the Asterisk Gateway Interface, and is licensed under the GNU Lesser 9 | General Public License (see COPYING for terms). 10 | 11 | This release (version 2) of the phpagi classes is a significant overhaul 12 | from the old version 1 library. API functions have been renamed and 13 | restructured. 14 | 15 | Version 1 of phpagi is no longer supported, but will continue to be 16 | available for historical purposes. We strongly encourage you to migrate 17 | to this new version. 18 | 19 | If you have developed software based around phpagi, we'd like to hear from 20 | you! Drop us a note, and indicate whether you'd like us to list your 21 | application on our website. 22 | 23 | FILES 24 | ----- 25 | phpagi.php - The main phpagi class. 26 | phpagi-asmanager.php - The Asterisk Manager class. 27 | phpagi-fastagi.php - FastAGI class. 28 | phpagi_1.php - Compatability class for 1.12 release 29 | 30 | docs/ - README files for the classes. 31 | api-docs/ - API Documentation (html) 32 | 33 | DOCS 34 | ---- 35 | README.phpagi - The main phpagi README 36 | README.phpagi-asmanager - The phpagi asterisk manager README 37 | README.phpagi-fastagi - phpagi fastagi README 38 | 39 | CHANGELOG - Change Log. 40 | 41 | phpagi.conf - An example configuration file for phpagi. 42 | fastagi.xinetd - xinetd.conf sample configuration for fastagi 43 | 44 | 45 | SUPPORT 46 | ------- 47 | 48 | Support for phpagi is available from the project website. 49 | 50 | * http://phpagi.sourceforge.net 51 | * http://sourceforge.net/projects/phpagi/ 52 | 53 | -------------------------------------------------------------------------------- /agi.php: -------------------------------------------------------------------------------- 1 | , David Eder 7 | * Copyright (c) 2005 - 2015 Schmooze Com, Inc 8 | * All Rights Reserved. 9 | * 10 | * This software is released under the terms of the GNU Public License v2 11 | * A copy of which is available from http://www.fsf.org/licenses/gpl.txt 12 | * 13 | * @package phpAGI 14 | */ 15 | namespace Asterisk\AMI; 16 | error_reporting(0); 17 | @ini_set('display_errors', 0); 18 | 19 | define('DEFAULT_PHPAGI_CONFIG', '/etc/asterisk/phpagi.conf'); 20 | 21 | define('AST_DIGIT_ANY', '0123456789#*'); 22 | 23 | define('AGIRES_OK', 200); 24 | 25 | define('AST_STATE_DOWN', 0); 26 | define('AST_STATE_RESERVED', 1); 27 | define('AST_STATE_OFFHOOK', 2); 28 | define('AST_STATE_DIALING', 3); 29 | define('AST_STATE_RING', 4); 30 | define('AST_STATE_RINGING', 5); 31 | define('AST_STATE_UP', 6); 32 | define('AST_STATE_BUSY', 7); 33 | define('AST_STATE_DIALING_OFFHOOK', 8); 34 | define('AST_STATE_PRERING', 9); 35 | define('AST_STATE_MUTE', 10); 36 | 37 | define('AUDIO_FILENO', 3); // STDERR_FILENO + 1 38 | 39 | /** 40 | * AGI class 41 | * 42 | * @package phpAGI 43 | * @link http://www.voip-info.org/wiki-Asterisk+agi 44 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 45 | * @example examples/input.php Get text input from the user and say it back 46 | * @example examples/ping.php Ping an IP address 47 | */ 48 | 49 | class AGI { 50 | /** 51 | * Request variables read in on initialization. 52 | * 53 | * Often contains any/all of the following: 54 | * agi_request - name of agi script 55 | * agi_channel - current channel 56 | * agi_language - current language 57 | * agi_type - channel type (SIP, ZAP, IAX, ...) 58 | * agi_uniqueid - unique id based on unix time 59 | * agi_callerid - callerID string 60 | * agi_dnid - dialed number id 61 | * agi_rdnis - referring DNIS number 62 | * agi_context - current context 63 | * agi_extension - extension dialed 64 | * agi_priority - current priority 65 | * agi_enhanced - value is 1.0 if started as an EAGI script 66 | * agi_accountcode - set by SetAccount in the dialplan 67 | * agi_network - value is yes if this is a fastagi 68 | * agi_network_script - name of the script to execute 69 | * 70 | * NOTE: program arguments are still in $_SERVER['argv']. 71 | * 72 | * @var array 73 | */ 74 | public $request; 75 | 76 | /** 77 | * Config variables 78 | * 79 | * @var array 80 | */ 81 | public $config; 82 | 83 | /** 84 | * Input Stream 85 | */ 86 | private $in = NULL; 87 | 88 | /** 89 | * Output Stream 90 | */ 91 | private $out = NULL; 92 | 93 | /** 94 | * FastAGI socket 95 | */ 96 | private $socket = NULL; 97 | 98 | /** 99 | * Audio Stream 100 | */ 101 | public $audio = NULL; 102 | 103 | /** 104 | * Constructor 105 | * 106 | * @param string $config is the name of the config file to parse 107 | * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['phpagi'] 108 | * @param object $socket The Connection socket. 109 | */ 110 | public function __construct($config=NULL, $optconfig=array(), $socket=NULL) { 111 | // load config 112 | if(!is_null($config) && file_exists($config)) { 113 | $this->config = parse_ini_file($config, true); 114 | } elseif(file_exists(DEFAULT_PHPAGI_CONFIG)) { 115 | $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); 116 | } 117 | 118 | // If optconfig is specified, stuff vals and vars into 'phpagi' config array. 119 | foreach($optconfig as $var=>$val) { 120 | $this->config['phpagi'][$var] = $val; 121 | } 122 | 123 | // add default values to config for uninitialized values 124 | if(!isset($this->config['phpagi']['debug'])) $this->config['phpagi']['debug'] = false; 125 | if(!isset($this->config['phpagi']['admin'])) $this->config['phpagi']['admin'] = NULL; 126 | $temp = sys_get_temp_dir(); 127 | if(!isset($this->config['phpagi']['tempdir'])) $this->config['phpagi']['tempdir'] = (!empty($temp) ? $temp : '/tmp'); 128 | 129 | ob_implicit_flush(true); 130 | 131 | // open input & output 132 | if(is_null($socket)) { 133 | $this->in = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); 134 | $this->out = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); 135 | } else { 136 | $this->socket = $socket; 137 | } 138 | 139 | // make sure temp folder exists 140 | if(!file_exists($this->config['phpagi']['tempdir'])) { 141 | mkdir($this->config['phpagi']['tempdir'], 0755, true); 142 | } 143 | 144 | // read the request 145 | $str = is_null($this->socket) ? fgets($this->in) : socket_read($this->socket, 4096, PHP_NORMAL_READ); 146 | while($str != "\n") { 147 | $this->request[substr($str, 0, strpos($str, ':'))] = trim(substr($str, strpos($str, ':') + 1)); 148 | $str = is_null($this->socket) ? fgets($this->in) : socket_read($this->socket, 4096, PHP_NORMAL_READ); 149 | } 150 | 151 | // open audio if eagi detected 152 | if($this->request['agi_enhanced'] == '1.0') { 153 | if(file_exists('/proc/' . getmypid() . '/fd/3')) { 154 | // this should work on linux 155 | $this->audio = fopen('/proc/' . getmypid() . '/fd/3', 'r'); 156 | } elseif(file_exists('/dev/fd/3')) { 157 | // this should work on BSD. may need to mount fdescfs if this fails 158 | $this->audio = fopen('/dev/fd/3', 'r'); 159 | } else { 160 | $this->conlog('Unable to open audio stream'); 161 | } 162 | 163 | if($this->audio) stream_set_blocking($this->audio, 0); 164 | } 165 | } 166 | 167 | // ********************************************************************************************************* 168 | // ** COMMANDS ** 169 | // ********************************************************************************************************* 170 | 171 | /** 172 | * Answer channel if not already in answer state. 173 | * 174 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_answer 175 | * 176 | * @return array, see evaluate for return information. ['result'] is 0 on success, -1 on failure. 177 | */ 178 | public function answer() { 179 | return $this->evaluate('ANSWER'); 180 | } 181 | 182 | /** 183 | * Get the status of the specified channel. If no channel name is specified, return the status of the current channel. 184 | * 185 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_channel+status 186 | * @param string $channel 187 | * @return array, see evaluate for return information. ['data'] contains description. 188 | */ 189 | public function channel_status($channel='') { 190 | $ret = $this->evaluate("CHANNEL STATUS $channel"); 191 | switch($ret['result']) { 192 | case -1: $ret['data'] = trim("There is no channel that matches $channel"); break; 193 | case AST_STATE_DOWN: $ret['data'] = 'Channel is down and available'; break; 194 | case AST_STATE_RESERVED: $ret['data'] = 'Channel is down, but reserved'; break; 195 | case AST_STATE_OFFHOOK: $ret['data'] = 'Channel is off hook'; break; 196 | case AST_STATE_DIALING: $ret['data'] = 'Digits (or equivalent) have been dialed'; break; 197 | case AST_STATE_RING: $ret['data'] = 'Line is ringing'; break; 198 | case AST_STATE_RINGING: $ret['data'] = 'Remote end is ringing'; break; 199 | case AST_STATE_UP: $ret['data'] = 'Line is up'; break; 200 | case AST_STATE_BUSY: $ret['data'] = 'Line is busy'; break; 201 | case AST_STATE_DIALING_OFFHOOK: $ret['data'] = 'Digits (or equivalent) have been dialed while offhook'; break; 202 | case AST_STATE_PRERING: $ret['data'] = 'Channel has detected an incoming call and is waiting for ring'; break; 203 | case AST_STATE_MUTE: $ret['data'] = 'Do not transmit voice data'; break; 204 | default: $ret['data'] = "Unknown ({$ret['result']})"; break; 205 | } 206 | return $ret; 207 | } 208 | 209 | /** 210 | * Deletes an entry in the Asterisk database for a given family and key. 211 | * 212 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_database+del 213 | * @param string $family 214 | * @param string $key 215 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. 216 | */ 217 | public function database_del($family, $key) { 218 | return $this->evaluate("DATABASE DEL \"$family\" \"$key\""); 219 | } 220 | 221 | /** 222 | * Deletes a family or specific keytree within a family in the Asterisk database. 223 | * 224 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_database+deltree 225 | * @param string $family 226 | * @param string $keytree 227 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise. 228 | */ 229 | public function database_deltree($family, $keytree='') { 230 | $cmd = "DATABASE DELTREE \"$family\""; 231 | if($keytree != '') $cmd .= " \"$keytree\""; 232 | return $this->evaluate($cmd); 233 | } 234 | 235 | /** 236 | * Retrieves an entry in the Asterisk database for a given family and key. 237 | * 238 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_database+get 239 | * @param string $family 240 | * @param string $key 241 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 failure. ['data'] holds the value 242 | */ 243 | public function database_get($family, $key) { 244 | return $this->evaluate("DATABASE GET \"$family\" \"$key\""); 245 | } 246 | 247 | /** 248 | * Adds or updates an entry in the Asterisk database for a given family, key, and value. 249 | * 250 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_database+put 251 | * @param string $family 252 | * @param string $key 253 | * @param string $value 254 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise 255 | */ 256 | public function database_put($family, $key, $value) { 257 | $value = str_replace("\n", '\n', addslashes($value)); 258 | return $this->evaluate("DATABASE PUT \"$family\" \"$key\" \"$value\""); 259 | } 260 | 261 | /** 262 | * Executes the specified Asterisk application with given options. 263 | * 264 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_exec 265 | * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands 266 | * @param string $application 267 | * @param mixed $options 268 | * @return array, see evaluate for return information. ['result'] is whatever the application returns, or -2 on failure to find application 269 | */ 270 | public function exec($application, $options) { 271 | if(is_array($options)) $options = join(',', $options); 272 | return $this->evaluate("EXEC $application $options"); 273 | } 274 | 275 | /** 276 | * Plays the given file and receives DTMF data. 277 | * 278 | * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, 279 | * while STREAM FILE returns immediately after the first DTMF digit is detected. 280 | * 281 | * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. 282 | * 283 | * If the user doesn't press any keys when the message plays, there is $timeout milliseconds 284 | * of silence then the command ends. 285 | * 286 | * The user has the opportunity to press a key at any time during the message or the 287 | * post-message silence. If the user presses a key while the message is playing, the 288 | * message stops playing. When the first key is pressed a timer starts counting for 289 | * $timeout milliseconds. Every time the user presses another key the timer is restarted. 290 | * The command ends when the counter goes to zero or the maximum number of digits is entered, 291 | * whichever happens first. 292 | * 293 | * If you don't specify a time out then a default timeout of 2000 is used following a pressed 294 | * digit. If no digits are pressed then 6 seconds of silence follow the message. 295 | * 296 | * If you don't specify $max_digits then the user can enter as many digits as they want. 297 | * 298 | * Pressing the # key has the same effect as the timer running out: the command ends and 299 | * any previously keyed digits are returned. A side effect of this is that there is no 300 | * way to read a # key using this command. 301 | * 302 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_get+data 303 | * @param string $filename file to play. Do not include file extension. 304 | * @param integer $timeout milliseconds 305 | * @param integer $max_digits 306 | * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. 307 | * 308 | * This differs from other commands with return DTMF as numbers representing ASCII characters. 309 | */ 310 | public function get_data($filename, $timeout=NULL, $max_digits=NULL) { 311 | return $this->evaluate(rtrim("GET DATA $filename $timeout $max_digits")); 312 | } 313 | 314 | /** 315 | * Fetch the value of a variable. 316 | * 317 | * Does not work with global variables. Does not work with some variables that are generated by modules. 318 | * 319 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_get+variable 320 | * @param string $variable name 321 | * @return array, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value. 322 | */ 323 | public function get_variable($variable) { 324 | return $this->evaluate("GET VARIABLE $variable"); 325 | } 326 | 327 | /** 328 | * https://wiki.asterisk.org/wiki/display/AST/AGICommand_get+full+variable 329 | * @param string $variable name 330 | * @return array, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value. 331 | */ 332 | function get_full_variable($variable) { 333 | return $this->evaluate("GET FULL VARIABLE $variable"); 334 | } 335 | 336 | /** 337 | * Hangup the specified channel. If no channel name is given, hang up the current channel. 338 | * 339 | * With power comes responsibility. Hanging up channels other than your own isn't something 340 | * that is done routinely. If you are not sure why you are doing so, then don't. 341 | * 342 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_hangup 343 | * @param string $channel 344 | * @return array, see evaluate for return information. ['result'] is 1 on success, -1 on failure. 345 | */ 346 | public function hangup($channel='') { 347 | return $this->evaluate("HANGUP $channel"); 348 | } 349 | 350 | /** 351 | * Does nothing. 352 | * 353 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_noop 354 | * @return array, see evaluate for return information. 355 | */ 356 | public function noop() { 357 | return $this->evaluate('NOOP'); 358 | } 359 | 360 | /** 361 | * Receive a character of text from a connected channel. Waits up to $timeout milliseconds for 362 | * a character to arrive, or infinitely if $timeout is zero. 363 | * 364 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_receive+char 365 | * @param integer $timeout milliseconds 366 | * @return array, see evaluate for return information. ['result'] is 0 on timeout or not supported, -1 on failure. Otherwise 367 | * it is the decimal value of the DTMF tone. Use chr() to convert to ASCII. 368 | */ 369 | public function receive_char($timeout=-1) { 370 | return $this->evaluate("RECEIVE CHAR $timeout"); 371 | } 372 | 373 | /** 374 | * Record sound to a file until an acceptable DTMF digit is received or a specified amount of 375 | * time has passed. Optionally the file BEEP is played before recording begins. 376 | * 377 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_record+file 378 | * @param string $file to record, without extension, often created in /var/lib/asterisk/sounds 379 | * @param string $format of the file. GSM and WAV are commonly used formats. MP3 is read-only and thus cannot be used. 380 | * @param string $escape_digits 381 | * @param integer $timeout is the maximum record time in milliseconds, or -1 for no timeout. 382 | * @param integer $offset to seek to without exceeding the end of the file. 383 | * @param boolean $beep 384 | * @param integer $silence number of seconds of silence allowed before the function returns despite the 385 | * lack of dtmf digits or reaching timeout. 386 | * @return array, see evaluate for return information. ['result'] is -1 on error, 0 on hangup, otherwise a decimal value of the 387 | * DTMF tone. Use chr() to convert to ASCII. 388 | */ 389 | public function record_file($file, $format, $escape_digits='', $timeout=-1, $offset=NULL, $beep=false, $silence=NULL) { 390 | $cmd = trim("RECORD FILE $file $format \"$escape_digits\" $timeout $offset"); 391 | if($beep) $cmd .= ' BEEP'; 392 | if(!is_null($silence)) $cmd .= " s=$silence"; 393 | return $this->evaluate($cmd); 394 | } 395 | 396 | /** 397 | * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. 398 | * 399 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_say+digits 400 | * @param integer $digits 401 | * @param string $escape_digits 402 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 403 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 404 | */ 405 | public function say_digits($digits, $escape_digits='') { 406 | return $this->evaluate("SAY DIGITS $digits \"$escape_digits\""); 407 | } 408 | 409 | /** 410 | * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. 411 | * 412 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_say+number 413 | * @param integer $number 414 | * @param string $escape_digits 415 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 416 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 417 | */ 418 | public function say_number($number, $escape_digits='') { 419 | return $this->evaluate("SAY NUMBER $number \"$escape_digits\""); 420 | } 421 | 422 | /** 423 | * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. 424 | * 425 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_say+phonetic 426 | * @param string $text 427 | * @param string $escape_digits 428 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 429 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 430 | */ 431 | public function say_phonetic($text, $escape_digits='') { 432 | return $this->evaluate("SAY PHONETIC $text \"$escape_digits\""); 433 | } 434 | 435 | /** 436 | * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. 437 | * 438 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_say+time 439 | * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). 440 | * @param string $escape_digits 441 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 442 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 443 | */ 444 | public function say_time($time=NULL, $escape_digits='') { 445 | if(is_null($time)) $time = time(); 446 | return $this->evaluate("SAY TIME $time \"$escape_digits\""); 447 | } 448 | 449 | /** 450 | * Send the specified image on a channel. 451 | * 452 | * Most channels do not support the transmission of images. 453 | * 454 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_send+image 455 | * @param string $image without extension, often in /var/lib/asterisk/images 456 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the image is sent or 457 | * channel does not support image transmission. 458 | */ 459 | public function send_image($image) { 460 | return $this->evaluate("SEND IMAGE $image"); 461 | } 462 | 463 | /** 464 | * Send the given text to the connected channel. 465 | * 466 | * Most channels do not support transmission of text. 467 | * 468 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_send+text 469 | * @param $text 470 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the text is sent or 471 | * channel does not support text transmission. 472 | */ 473 | public function send_text($text) { 474 | return $this->evaluate("SEND TEXT \"$text\""); 475 | } 476 | 477 | /** 478 | * Cause the channel to automatically hangup at $time seconds in the future. 479 | * If $time is 0 then the autohangup feature is disabled on this channel. 480 | * 481 | * If the channel is hungup prior to $time seconds, this setting has no effect. 482 | * 483 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_set+autohangup 484 | * @param integer $time until automatic hangup 485 | * @return array, see evaluate for return information. 486 | */ 487 | public function set_autohangup($time=0) { 488 | return $this->evaluate("SET AUTOHANGUP $time"); 489 | } 490 | 491 | /** 492 | * Changes the caller ID of the current channel. 493 | * 494 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_set+callerid 495 | * @param string $cid example: "John Smith"<1234567> 496 | * This command will let you take liberties with the but the format shown in the example above works 497 | * well: the name enclosed in double quotes followed immediately by the number inside angle brackets. If there is no name then 498 | * you can omit it. If the name contains no spaces you can omit the double quotes around it. The number must follow the name 499 | * immediately; don't put a space between them. The angle brackets around the number are necessary; if you omit them the 500 | * number will be considered to be part of the name. 501 | * @return array, see evaluate for return information. 502 | */ 503 | function set_callerid($cid) { 504 | return $this->evaluate("SET CALLERID $cid"); 505 | } 506 | 507 | /** 508 | * Sets the context for continuation upon exiting the application. 509 | * 510 | * Setting the context does NOT automatically reset the extension and the priority; if you want to start at the top of the new 511 | * context you should set extension and priority yourself. 512 | * 513 | * If you specify a non-existent context you receive no error indication (['result'] is still 0) but you do get a 514 | * warning message on the Asterisk console. 515 | * 516 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_set+context 517 | * @param string $context 518 | * @return array, see evaluate for return information. 519 | */ 520 | public function set_context($context) { 521 | return $this->evaluate("SET CONTEXT $context"); 522 | } 523 | 524 | /** 525 | * Set the extension to be used for continuation upon exiting the application. 526 | * 527 | * Setting the extension does NOT automatically reset the priority. If you want to start with the first priority of the 528 | * extension you should set the priority yourself. 529 | * 530 | * If you specify a non-existent extension you receive no error indication (['result'] is still 0) but you do 531 | * get a warning message on the Asterisk console. 532 | * 533 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_set+extension 534 | * @param string $extension 535 | * @return array, see evaluate for return information. 536 | */ 537 | public function set_extension($extension) { 538 | return $this->evaluate("SET EXTENSION $extension"); 539 | } 540 | 541 | /** 542 | * Enable/Disable Music on hold generator. 543 | * 544 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_set+music 545 | * @param boolean $enabled 546 | * @param string $class 547 | * @return array, see evaluate for return information. 548 | */ 549 | public function set_music($enabled=true, $class='') { 550 | $enabled = ($enabled) ? 'ON' : 'OFF'; 551 | return $this->evaluate("SET MUSIC $enabled $class"); 552 | } 553 | 554 | /** 555 | * Set the priority to be used for continuation upon exiting the application. 556 | * 557 | * If you specify a non-existent priority you receive no error indication (['result'] is still 0) 558 | * and no warning is issued on the Asterisk console. 559 | * 560 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_set+priority 561 | * @param integer $priority 562 | * @return array, see evaluate for return information. 563 | */ 564 | public function set_priority($priority) { 565 | return $this->evaluate("SET PRIORITY $priority"); 566 | } 567 | 568 | /** 569 | * Sets a variable to the specified value. The variables so created can later be used by later using ${} 570 | * in the dialplan. 571 | * 572 | * These variables live in the channel Asterisk creates when you pickup a phone and as such they are both local and temporary. 573 | * Variables created in one channel can not be accessed by another channel. When you hang up the phone, the channel is deleted 574 | * and any variables in that channel are deleted as well. 575 | * 576 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_set+variable 577 | * @param string $variable is case sensitive 578 | * @param string $value 579 | * @return array, see evaluate for return information. 580 | */ 581 | public function set_variable($variable, $value) { 582 | $value = str_replace("\n", '\n', addslashes($value)); 583 | return $this->evaluate("SET VARIABLE $variable \"$value\""); 584 | } 585 | 586 | /** 587 | * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA 588 | * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of 589 | * digits before returning. 590 | * 591 | * @example examples/ping.php Ping an IP address 592 | * 593 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_stream+file 594 | * @param string $filename without extension, often in /var/lib/asterisk/sounds 595 | * @param string $escape_digits 596 | * @param integer $offset 597 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 598 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 599 | */ 600 | public function stream_file($filename, $escape_digits='', $offset=0) { 601 | return $this->evaluate("STREAM FILE $filename \"$escape_digits\" $offset"); 602 | } 603 | 604 | /** 605 | * Enable or disable TDD transmission/reception on the current channel. 606 | * 607 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_tdd+mode 608 | * @param string $setting can be on, off or mate 609 | * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 if the channel is not TDD capable. 610 | */ 611 | public function tdd_mode($setting) { 612 | return $this->evaluate("TDD MODE $setting"); 613 | } 614 | 615 | /** 616 | * Sends $message to the Asterisk console via the 'verbose' message system. 617 | * 618 | * If the Asterisk verbosity level is $level or greater, send $message to the console. 619 | * 620 | * The Asterisk verbosity system works as follows. The Asterisk user gets to set the desired verbosity at startup time or later 621 | * using the console 'set verbose' command. Messages are displayed on the console if their verbose level is less than or equal 622 | * to desired verbosity set by the user. More important messages should have a low verbose level; less important messages 623 | * should have a high verbose level. 624 | * 625 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_verbose 626 | * @param string $message 627 | * @param integer $level from 1 to 4 628 | * @return array, see evaluate for return information. 629 | */ 630 | public function verbose($message, $level=1) { 631 | foreach(explode("\n", str_replace("\r\n", "\n", print_r($message, true))) as $msg) { 632 | // Enable for extra logging. 633 | // @syslog(LOG_WARNING, $msg); 634 | $ret = $this->evaluate("VERBOSE \"$msg\" $level"); 635 | } 636 | return $ret; 637 | } 638 | 639 | /** 640 | * Waits up to $timeout milliseconds for channel to receive a DTMF digit. 641 | * 642 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AGICommand_wait+for+digit 643 | * @param integer $timeout in millisecons. Use -1 for the timeout value if you want the call to wait indefinitely. 644 | * @return array, see evaluate for return information. ['result'] is 0 if wait completes with no 645 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 646 | */ 647 | public function wait_for_digit($timeout=-1) { 648 | return $this->evaluate("WAIT FOR DIGIT $timeout"); 649 | } 650 | 651 | 652 | // ********************************************************************************************************* 653 | // ** APPLICATIONS ** 654 | // ********************************************************************************************************* 655 | 656 | /** 657 | * Executes an AGI compliant application. 658 | * 659 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+Application_AGI 660 | * @param string $command 661 | * @param string $args 662 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or if application requested hangup, or 0 on non-hangup exit. 663 | */ 664 | public function exec_agi($command, $args) { 665 | return $this->exec("AGI $command", $args); 666 | } 667 | 668 | /** 669 | * Set Account Code 670 | * 671 | * Set the channel account code for billing purposes. 672 | * 673 | * @link https://wiki.asterisk.org/wiki/display/AST/CDR+Applications 674 | * @param string $accountcode 675 | * @return array, see evaluate for return information. 676 | */ 677 | public function exec_setaccountcode($accountcode) { 678 | return $this->exec('SetAccount', $accountcode); 679 | } 680 | 681 | /** 682 | * SIPAddHeader 683 | * 684 | * Adds a header to a SIP call placed with DIAL. 685 | * Remember to use the X-header if you are adding non-standard SIP headers, like X-Asterisk-Accountcode:. Use this with care. 686 | * Adding the wrong headers may jeopardize the SIP dialog. 687 | * Always returns 0. 688 | * 689 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+Application_SIPAddHeader 690 | * @param string $header SIP Header 691 | * @param string $value SIP Header Value 692 | * @return array, see evaluate for return information. 693 | */ 694 | public function exec_sipaddheader($header, $value) { 695 | return $this->exec('SIPAddHeader', '"'.$header.":".$value.'"'); 696 | } 697 | 698 | /** 699 | * Alertinfo 700 | * 701 | * @param string $value SIP Alertinfo to set 702 | * @return array, see evaluate for return information. 703 | * @TODO this needs to be in a higher level class 704 | */ 705 | public function set_alertinfo($value) { 706 | return $this->exec_sipaddheader('Alert-Info',$value); 707 | } 708 | 709 | /** 710 | * Dial. 711 | * 712 | * Dial takes input from ${VXML_URL} to send XML Url to Cisco 7960 713 | * Dial takes input from ${ALERT_INFO} to set ring cadence for Cisco phones 714 | * Dial returns ${CAUSECODE}: If the dial failed, this is the errormessage. 715 | * Dial returns ${DIALSTATUS}: Text code returning status of last dial attempt. 716 | * 717 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+Application_Dial 718 | * @param string $type 719 | * @param string $identifier 720 | * @param integer $timeout 721 | * @param string $options 722 | * @param string $url 723 | * @return array, see evaluate for return information. 724 | */ 725 | public function exec_dial($type, $identifier, $timeout=NULL, $options=NULL, $url=NULL) { 726 | return $this->exec('Dial', trim("$type/$identifier,$timeout,$options,$url", ',')); 727 | } 728 | 729 | /** 730 | * Goto. 731 | * 732 | * This function takes three arguments: context,extension, and priority, but the leading arguments 733 | * are optional, not the trailing arguments. Thuse goto($z) sets the priority to $z. 734 | * 735 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+Application_Goto 736 | * @param string $a 737 | * @param string $b; 738 | * @param string $c; 739 | * @return array, see evaluate for return information. 740 | */ 741 | public function exec_goto($a, $b=NULL, $c=NULL) { 742 | return $this->exec('Goto', trim("$a,$b,$c", ',')); 743 | } 744 | 745 | /** 746 | * Evaluate an AGI command. 747 | * 748 | * @param string $command 749 | * @return array ('code'=>$code, 'result'=>$result, 'data'=>$data) 750 | */ 751 | private function evaluate($command) { 752 | $broken = array('code'=>500, 'result'=>-1, 'data'=>''); 753 | 754 | // FREEPBX-7204 - Discard any cruft, errors, etc, that may have been 755 | // produced by Asterisk on startup. This is single threaded here, so 756 | // dropping anything pending hopefully shouldn't cause issues. 757 | stream_set_blocking($this->in, 0); 758 | while (fgets($this->in) !== false) { } // Discard 759 | stream_set_blocking($this->in, 1); 760 | 761 | // write command 762 | if(is_null($this->socket)) { 763 | if(!@fwrite($this->out, trim($command) . "\n")) { 764 | return $broken; 765 | } 766 | fflush($this->out); 767 | } elseif(socket_write($this->socket, trim($command) . "\n") === false) { 768 | return $broken; 769 | } 770 | 771 | // Read result. Occasionally, a command returns a string followed by an extra new line. 772 | // When this happens, our script will ignore the new line, but it will still be in the 773 | // buffer. So, if we get a blank line, it is probably the result of a previous 774 | // command. We read until we get a valid result or asterisk hangs up. One offending 775 | // command is SEND TEXT. 776 | $count = 0; 777 | do { 778 | $str = is_null($this->socket) ? @fgets($this->in, 4096) : @socket_read($this->socket, 4096, PHP_NORMAL_READ); 779 | } while($str == '' && $count++ < 5); 780 | 781 | if($count >= 5) { 782 | // $this->conlog("evaluate error on read for $command"); 783 | return $broken; 784 | } 785 | 786 | // parse result 787 | $ret['code'] = substr($str, 0, 3); 788 | $str = trim(substr($str, 3)); 789 | 790 | // we have a multiline response! 791 | if($str{0} == '-') { 792 | $count = 0; 793 | $str = substr($str, 1) . "\n"; 794 | 795 | $line = is_null($this->socket) ? @fgets($this->in, 4096) : @socket_read($this->socket, 4096, PHP_NORMAL_READ); 796 | while(substr($line, 0, 3) != $ret['code'] && $count < 5) { 797 | $str .= $line; 798 | $line = is_null($this->socket) ? @fgets($this->in, 4096) : @socket_read($this->socket, 4096, PHP_NORMAL_READ); 799 | $count = (trim($line) == '') ? $count + 1 : 0; 800 | } 801 | if($count >= 5) { 802 | // $this->conlog("evaluate error on multiline read for $command"); 803 | return $broken; 804 | } 805 | } 806 | 807 | $ret['result'] = NULL; 808 | $ret['data'] = ''; 809 | // some sort of error 810 | if($ret['code'] != AGIRES_OK) { 811 | $ret['data'] = $str; 812 | $this->conlog(print_r($ret, true)); 813 | // normal AGIRES_OK response 814 | } else { 815 | $parse = explode(' ', trim($str)); 816 | $in_token = false; 817 | foreach($parse as $token) { 818 | // we previously hit a token starting with '(' but not ending in ')' 819 | if($in_token) { 820 | $tmp = trim($token); 821 | $tmp = $tmp{0} == '(' ? substr($tmp,1):$tmp; 822 | $tmp = substr($tmp,-1) == ')' ? substr($tmp,0,strlen($tmp)-1):$tmp; 823 | $ret['data'] .= ' ' . trim($tmp); 824 | if($token{strlen($token)-1} == ')') { 825 | $in_token = false; 826 | } 827 | } elseif($token{0} == '(') { 828 | if($token{strlen($token)-1} != ')') { 829 | $in_token = true; 830 | } 831 | $tmp = trim(substr($token,1)); 832 | $tmp = $in_token ? $tmp : substr($tmp,0,strlen($tmp)-1); 833 | $ret['data'] .= ' ' . trim($tmp); 834 | } elseif(strpos($token, '=')) { 835 | $token = explode('=', $token); 836 | $ret[$token[0]] = $token[1]; 837 | } elseif($token != '') { 838 | $ret['data'] .= ' ' . $token; 839 | } 840 | } 841 | $ret['data'] = trim($ret['data']); 842 | } 843 | 844 | // log some errors 845 | if($ret['result'] < 0) { 846 | $this->conlog("$command returned {$ret['result']}"); 847 | } 848 | 849 | return $ret; 850 | } 851 | 852 | /** 853 | * Log to console if debug mode. 854 | * 855 | * @example examples/ping.php Ping an IP address 856 | * 857 | * @param string $str 858 | * @param integer $vbl verbose level 859 | */ 860 | protected function conlog($str, $vbl=1) { 861 | static $busy = false; 862 | 863 | if(isset($this->config['phpagi'], $this->config['phpagi']['debug']) && $this->config['phpagi']['debug'] != false) { 864 | if(!$busy) { // no conlogs inside conlog!!! 865 | $busy = true; 866 | $this->verbose($str, $vbl); 867 | $busy = false; 868 | } 869 | } 870 | } 871 | } 872 | -------------------------------------------------------------------------------- /derived.php: -------------------------------------------------------------------------------- 1 | , David Eder 7 | * Copyright (c) 2005 - 2015 Schmooze Com, Inc 8 | * All Rights Reserved. 9 | * 10 | * This software is released under the terms of the GNU Public License v2 11 | * A copy of which is available from http://www.fsf.org/licenses/gpl.txt 12 | * 13 | * @package phpAGI 14 | */ 15 | 16 | if(!class_exists('AGI')) { 17 | require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); 18 | } 19 | 20 | 21 | /** 22 | * AGI class 23 | * 24 | * @package phpAGI 25 | * @link http://www.voip-info.org/wiki-Asterisk+agi 26 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 27 | * @example examples/input.php Get text input from the user and say it back 28 | * @example examples/ping.php Ping an IP address 29 | */ 30 | class derived extends AGI { 31 | /** 32 | * Goto_dest - Set context, extension and priority. 33 | * 34 | * @param string $context 35 | * @param string $extension 36 | * @param string $priority 37 | */ 38 | function goto_dest($context, $extension='s', $priority=1) { 39 | $this->set_context($context); 40 | $this->set_extension($extension); 41 | $this->set_priority($priority); 42 | } 43 | 44 | /** 45 | * Parse caller id. 46 | * 47 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 48 | * @example examples/input.php Get text input from the user and say it back 49 | * 50 | * "name" 51 | * 52 | * @param string $callerid 53 | * @return array('Name'=>$name, 'Number'=>$number) 54 | */ 55 | function parse_callerid($callerid=NULL) { 56 | if(is_null($callerid)) { 57 | $callerid = $this->request['agi_callerid']; 58 | } 59 | 60 | $ret = array('name'=>'', 'protocol'=>'', 'username'=>'', 'host'=>'', 'port'=>''); 61 | $callerid = trim($callerid); 62 | 63 | if($callerid{0} == '"' || $callerid{0} == "'") { 64 | $d = $callerid{0}; 65 | $callerid = explode($d, substr($callerid, 1)); 66 | $ret['name'] = array_shift($callerid); 67 | $callerid = join($d, $callerid); 68 | } 69 | 70 | $callerid = explode('@', trim($callerid, '<> ')); 71 | $username = explode(':', array_shift($callerid)); 72 | if(count($username) == 1) { 73 | $ret['username'] = $username[0]; 74 | } else { 75 | $ret['protocol'] = array_shift($username); 76 | $ret['username'] = join(':', $username); 77 | } 78 | 79 | $callerid = join('@', $callerid); 80 | $host = explode(':', $callerid); 81 | if(count($host) == 1) { 82 | $ret['host'] = $host[0]; 83 | } else { 84 | $ret['host'] = array_shift($host); 85 | $ret['port'] = join(':', $host); 86 | } 87 | 88 | return $ret; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /examples/README: -------------------------------------------------------------------------------- 1 | To execute any of the examples, it must set up an extension in your dialplan. 2 | 3 | For example: 4 | 5 | exten => *111,1,agi(dtmf.php) 6 | 7 | dtmf.php must be in your script directory, often /var/lib/asterisk/agi-bin/. 8 | dtmf.php must be executable. (chmod 755 dtmf.php) 9 | dtmf.php must have the correct path to php in the first line. 10 | 11 | ----------------------------------------------------------------------------- 12 | Examples: 13 | 14 | dtmf.php is a simple script that reads dtmf tones and reads them back to you. 15 | 16 | input.php is a simple text input script. See documentation on the text_input 17 | function. 18 | 19 | my_ip.php is a simple example of using the manager api from within an agi. It 20 | assumes you are connecting with a SIP channel. 21 | 22 | ping.php is a program that pings a host from agi. 23 | 24 | ring.php is a standalone program that rings a sip extension. 25 | 26 | sip_show_peer.php is a standalone program that use the manager api to list 27 | sip peers. 28 | -------------------------------------------------------------------------------- /examples/beep.gsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangoma/phpagi/d6e2b1af10b5c068ea50616a339d772c89002d90/examples/beep.gsm -------------------------------------------------------------------------------- /examples/dtmf.php: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php -q 2 | answer(); 9 | 10 | $cid = $agi->parse_callerid(); 11 | $agi->text2wav("Hello, {$cid['name']}."); 12 | do 13 | { 14 | $agi->text2wav('Enter some numbers and then press the pound key. Press 1 1 1 followed by the pound key to quit.'); 15 | $result = $agi->get_data('beep', 3000, 20); 16 | $keys = $result['result']; 17 | $agi->text2wav("You entered $keys"); 18 | } while($keys != '111'); 19 | $agi->text2wav('Goodbye'); 20 | $agi->hangup(); 21 | ?> 22 | -------------------------------------------------------------------------------- /examples/input.php: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php -q 2 | answer(); 8 | $cid = $agi->parse_callerid(); 9 | $agi->text2wav("Hello, {$cid['name']}. Let's enter some text."); 10 | $text = $agi->text_input('UPPERCASE'); 11 | $agi->text2wav("You entered $text"); 12 | $agi->text2wav('Goodbye'); 13 | $agi->hangup(); 14 | ?> 15 | -------------------------------------------------------------------------------- /examples/my_ip.php: -------------------------------------------------------------------------------- 1 | new_AsteriskManager(); 7 | if($asm->connect()) 8 | { 9 | $peer = $asm->command("sip show peer $peer"); 10 | $asm->disconnect(); 11 | 12 | if(!strpos($peer['data'], ':')) 13 | echo $peer['data']; 14 | else 15 | { 16 | $data = array(); 17 | foreach(explode("\n", $peer['data']) as $line) 18 | { 19 | $a = strpos('z'.$line, ':') - 1; 20 | if($a >= 0) $data[trim(substr($line, 0, $a))] = trim(substr($line, $a + 1)); 21 | } 22 | } 23 | 24 | if(isset($data['Addr->IP'])) 25 | { 26 | $ip = explode(' ', trim($data['Addr->IP'])); 27 | $ip = $ip[0]; 28 | } 29 | } 30 | $agi->text2wav("Your IP address is $ip"); 31 | } 32 | ?> 33 | -------------------------------------------------------------------------------- /examples/ping.gsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangoma/phpagi/d6e2b1af10b5c068ea50616a339d772c89002d90/examples/ping.gsm -------------------------------------------------------------------------------- /examples/ping.php: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php -q 2 | answer(); 9 | 10 | 11 | // Play the "Enter the host you wish to ping, followed by the pound sign" prompt 12 | // and then play the beep. 13 | $agi->stream_file('ping'); 14 | $result = $agi->get_data('beep', 3000, 20); 15 | $ip = str_replace('*', '.', $result['result']); 16 | 17 | /* Danger Will Robinson! This does NOT properly escape the ping command! 18 | * Someone could subvert your system if you don't fix this! - NO WARRANTY :P */ 19 | $execstr = "/bin/ping -c 5 -q -w 9 $ip|grep transmitted"; 20 | 21 | // be polite. 22 | $agi->stream_file('thanks', '#'); 23 | 24 | $p = popen($execstr, 'r'); 25 | if($p == FALSE) 26 | { 27 | $agi->text2wav("Failed to ping $ip"); 28 | $agi->conlog("Failed to ping $execstr"); 29 | } 30 | else 31 | { 32 | $str = ''; 33 | while(!feof($p)) 34 | { 35 | $r = fgets($p, 1024); 36 | if(!$r) break; 37 | $str .= $r; 38 | } 39 | 40 | // a minor hack. 41 | $str = str_replace('ms', 'milli-seconds', $str); 42 | 43 | // have festival read back the ping results. 44 | $agi->text2wav("$ip - $str"); 45 | } 46 | 47 | $agi->hangup(); 48 | ?> 49 | -------------------------------------------------------------------------------- /examples/ring.php: -------------------------------------------------------------------------------- 1 | connect()) 8 | { 9 | $call = $asm->send_request('Originate', 10 | array('Channel'=>"SIP/$number", 11 | 'Context'=>'default', 12 | 'Priority'=>1, 13 | 'Callerid'=>$number)); 14 | $asm->disconnect(); 15 | } 16 | ?> 17 | -------------------------------------------------------------------------------- /examples/sip_show_peer.php: -------------------------------------------------------------------------------- 1 | connect()) 12 | { 13 | $peer = $asm->command("sip show peer {$_SERVER['argv'][1]}"); 14 | if(!strpos($peer['data'], ':')) 15 | echo $peer['data']; 16 | else 17 | { 18 | $data = array(); 19 | foreach(explode("\n", $peer['data']) as $line) 20 | { 21 | $a = strpos('z'.$line, ':') - 1; 22 | if($a >= 0) $data[trim(substr($line, 0, $a))] = trim(substr($line, $a + 1)); 23 | } 24 | print_r($data); 25 | } 26 | 27 | $asm->disconnect(); 28 | } 29 | ?> 30 | -------------------------------------------------------------------------------- /examples/thanks.gsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangoma/phpagi/d6e2b1af10b5c068ea50616a339d772c89002d90/examples/thanks.gsm -------------------------------------------------------------------------------- /examples/weather.php: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php -q 2 | answer(); 8 | 9 | $agi->text2wav('Please enter your zip code.'); 10 | $result = $agi->get_data('beep', 3000, 5); 11 | $search = $result['result']; 12 | 13 | $db = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'weather.txt'; 14 | $min = 0; $max = filesize($db); 15 | $fp = fopen($db, 'r'); 16 | do 17 | { 18 | $mid = floor(($min + $max) / 2); 19 | fseek($fp, $mid); 20 | fgets($fp, 4096); 21 | list($zip, $city, $state, $latitude, $longitude, $station) = explode("\t", trim(fgets($fp, 4096))); 22 | if($search < $zip) 23 | $max = $mid; 24 | elseif($search > $zip) 25 | $min = $mid; 26 | } while($max - $min > 1 && $zip != $search); 27 | fclose($fp); 28 | 29 | if($search != $zip) 30 | $text = "I could not find the weather station for $zip"; 31 | else 32 | { 33 | $xml = join('', file("http://www.nws.noaa.gov/data/current_obs/$station.xml")); 34 | $vals = $index = NULL; 35 | $p = xml_parser_create(); 36 | xml_parser_set_option($p, XML_OPTION_SKIP_WHITE, 1); 37 | xml_parse_into_struct($p, $xml, $vals, $index); 38 | xml_parser_free($p); 39 | $i = 0; 40 | $data = get_children($vals, $i); 41 | 42 | $text = "The closes weather station to $city is at {$data['LOCATION']}. "; 43 | $text .= "It is currently {$data['WEATHER']}. The temperature is {$data['TEMP_F']} degrees"; 44 | if($data['WIND_MPH'] > 0) 45 | $text .= " with wind from the {$data['WIND_DIR']} at {$data['WIND_MPH']} miles per hour"; 46 | if($data['WINDCHILL_F'] != 'Not Applicable') 47 | $text .= ". The wind chill is {$data['WINDCHILL_F']} degrees"; 48 | if($data['HEAT_INDEX_F'] != 'Not Applicable') 49 | $text .= ". The heat index is {$data['HEAT_INDEX_F']}"; 50 | $text .= '.'; 51 | } 52 | 53 | $agi->text2wav($text); 54 | 55 | $agi->text2wav('Goodbye'); 56 | $agi->hangup(); 57 | 58 | 59 | function get_children($vals, &$i) 60 | { 61 | $ret = array(); 62 | for(++$i; $i < count($vals); $i++) 63 | { 64 | if(isset($vals[$i]['attributes']['NAME'])) 65 | $name = $vals[$i]['attributes']['NAME']; 66 | else 67 | $name = $vals[$i]['tag']; 68 | 69 | if($name != '' && $vals[$i]['type'] == 'open') 70 | $rt[$name][] = get_children($vals, $i); 71 | elseif($vals[$i]['type'] == 'close') 72 | { 73 | if(isset($rt)) foreach($rt as $key=>$val) 74 | { 75 | if(count($val) == 1) 76 | $ret[$key] = $val[0]; 77 | else 78 | $ret[$key] = $val; 79 | } 80 | return $ret; 81 | } 82 | elseif($name != '') 83 | { 84 | if(isset($vals[$i]['attributes']['VALUE'])) 85 | $rt[$name][] = $vals[$i]['attributes']['VALUE']; 86 | elseif(isset($vals[$i]['value'])) 87 | $rt[$name][] = $vals[$i]['value']; 88 | } 89 | } 90 | return $ret; 91 | } 92 | ?> 93 | -------------------------------------------------------------------------------- /examples/weather.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangoma/phpagi/d6e2b1af10b5c068ea50616a339d772c89002d90/examples/weather.txt.gz -------------------------------------------------------------------------------- /fastpass.php: -------------------------------------------------------------------------------- 1 | , David Eder 7 | * Copyright (c) 2005 - 2015 Schmooze Com, Inc 8 | * All Rights Reserved. 9 | * 10 | * This software is released under the terms of the GNU Public License v2 11 | * A copy of which is available from http://www.fsf.org/licenses/gpl.txt 12 | * 13 | * @package phpAGI 14 | */ 15 | namespace Asterisk\AMI; 16 | if(!class_exists('AGI')) { 17 | require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); 18 | } 19 | 20 | 21 | /** 22 | * AGI class 23 | * 24 | * @package phpAGI 25 | * @link http://www.voip-info.org/wiki-Asterisk+agi 26 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 27 | * @example examples/input.php Get text input from the user and say it back 28 | * @example examples/ping.php Ping an IP address 29 | */ 30 | class fastpass extends AGI { 31 | /** 32 | * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel. 33 | * Return early if $buffer is adequate for request. 34 | * 35 | * @link http://www.voip-info.org/wiki-say+digits 36 | * @param string $buffer 37 | * @param integer $digits 38 | * @param string $escape_digits 39 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 40 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 41 | */ 42 | public function fastpass_say_digits(&$buffer, $digits, $escape_digits='') { 43 | $proceed = false; 44 | if($escape_digits != '' && $buffer != '') { 45 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) 46 | $proceed = true; 47 | } 48 | if($buffer == '' || $proceed) { 49 | $res = $this->say_digits($digits, $escape_digits); 50 | if($res['code'] == AGIRES_OK && $res['result'] > 0) { 51 | $buffer .= chr($res['result']); 52 | } 53 | return $res; 54 | } 55 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1})); 56 | } 57 | 58 | /** 59 | * Say the given number, returning early if any of the given DTMF escape digits are received on the channel. 60 | * Return early if $buffer is adequate for request. 61 | * 62 | * @link http://www.voip-info.org/wiki-say+number 63 | * @param string $buffer 64 | * @param integer $number 65 | * @param string $escape_digits 66 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 67 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 68 | */ 69 | public function fastpass_say_number(&$buffer, $number, $escape_digits='') { 70 | $proceed = false; 71 | if($escape_digits != '' && $buffer != '') { 72 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) { 73 | $proceed = true; 74 | } 75 | } 76 | if($buffer == '' || $proceed) { 77 | $res = $this->say_number($number, $escape_digits); 78 | if($res['code'] == AGIRES_OK && $res['result'] > 0) { 79 | $buffer .= chr($res['result']); 80 | } 81 | return $res; 82 | } 83 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1})); 84 | } 85 | 86 | /** 87 | * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel. 88 | * Return early if $buffer is adequate for request. 89 | * 90 | * @link http://www.voip-info.org/wiki-say+phonetic 91 | * @param string $buffer 92 | * @param string $text 93 | * @param string $escape_digits 94 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 95 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 96 | */ 97 | public function fastpass_say_phonetic(&$buffer, $text, $escape_digits='') { 98 | $proceed = false; 99 | if($escape_digits != '' && $buffer != '') { 100 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) { 101 | $proceed = true; 102 | } 103 | } 104 | if($buffer == '' || $proceed) { 105 | $res = $this->say_phonetic($text, $escape_digits); 106 | if($res['code'] == AGIRES_OK && $res['result'] > 0) 107 | $buffer .= chr($res['result']); 108 | return $res; 109 | } 110 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1})); 111 | } 112 | 113 | /** 114 | * Say a given time, returning early if any of the given DTMF escape digits are received on the channel. 115 | * Return early if $buffer is adequate for request. 116 | * 117 | * @link http://www.voip-info.org/wiki-say+time 118 | * @param string $buffer 119 | * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC). 120 | * @param string $escape_digits 121 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 122 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 123 | */ 124 | function fastpass_say_time(&$buffer, $time=NULL, $escape_digits='') { 125 | $proceed = false; 126 | if($escape_digits != '' && $buffer != '') { 127 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) { 128 | $proceed = true; 129 | } 130 | } 131 | if($buffer == '' || $proceed) { 132 | $res = $this->say_time($time, $escape_digits); 133 | if($res['code'] == AGIRES_OK && $res['result'] > 0) { 134 | $buffer .= chr($res['result']); 135 | } 136 | return $res; 137 | } 138 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1})); 139 | } 140 | 141 | /** 142 | * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA 143 | * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of 144 | * digits before returning. 145 | * Return early if $buffer is adequate for request. 146 | * 147 | * @link http://www.voip-info.org/wiki-stream+file 148 | * @param string $buffer 149 | * @param string $filename without extension, often in /var/lib/asterisk/sounds 150 | * @param string $escape_digits 151 | * @param integer $offset 152 | * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no 153 | * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII. 154 | */ 155 | function fastpass_stream_file(&$buffer, $filename, $escape_digits='', $offset=0) { 156 | $proceed = false; 157 | if($escape_digits != '' && $buffer != '') { 158 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) { 159 | $proceed = true; 160 | } 161 | } 162 | if($buffer == '' || $proceed) { 163 | $res = $this->stream_file($filename, $escape_digits, $offset); 164 | if($res['code'] == AGIRES_OK && $res['result'] > 0) { 165 | $buffer .= chr($res['result']); 166 | } 167 | return $res; 168 | } 169 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}), 'endpos'=>0); 170 | } 171 | 172 | /** 173 | * Use festival to read text. 174 | * Return early if $buffer is adequate for request. 175 | * 176 | * @link http://www.cstr.ed.ac.uk/projects/festival/ 177 | * @param string $buffer 178 | * @param string $text 179 | * @param string $escape_digits 180 | * @param integer $frequency 181 | * @return array, see evaluate for return information. 182 | */ 183 | function fastpass_text2wav(&$buffer, $text, $escape_digits='', $frequency=8000) { 184 | $proceed = false; 185 | if($escape_digits != '' && $buffer != '') { 186 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) { 187 | $proceed = true; 188 | } 189 | } 190 | if($buffer == '' || $proceed) { 191 | $res = $this->text2wav($text, $escape_digits, $frequency); 192 | if($res['code'] == AGIRES_OK && $res['result'] > 0) { 193 | $buffer .= chr($res['result']); 194 | } 195 | return $res; 196 | } 197 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}), 'endpos'=>0); 198 | } 199 | 200 | /** 201 | * Use Cepstral Swift to read text. 202 | * Return early if $buffer is adequate for request. 203 | * 204 | * @link http://www.cepstral.com/ 205 | * @param string $buffer 206 | * @param string $text 207 | * @param string $escape_digits 208 | * @param integer $frequency 209 | * @return array, see evaluate for return information. 210 | */ 211 | function fastpass_swift(&$buffer, $text, $escape_digits='', $frequency=8000, $voice=NULL) { 212 | $proceed = false; 213 | if($escape_digits != '' && $buffer != '') { 214 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) 215 | $proceed = true; 216 | } 217 | if($buffer == '' || $proceed) { 218 | $res = $this->swift($text, $escape_digits, $frequency, $voice); 219 | if($res['code'] == AGIRES_OK && $res['result'] > 0) { 220 | $buffer .= chr($res['result']); 221 | } 222 | return $res; 223 | } 224 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}), 'endpos'=>0); 225 | } 226 | 227 | /** 228 | * Say Puncutation in a string. 229 | * Return early if $buffer is adequate for request. 230 | * 231 | * @param string $buffer 232 | * @param string $text 233 | * @param string $escape_digits 234 | * @param integer $frequency 235 | * @return array, see evaluate for return information. 236 | */ 237 | function fastpass_say_punctuation(&$buffer, $text, $escape_digits='', $frequency=8000) { 238 | $proceed = false; 239 | if($escape_digits != '' && $buffer != '') { 240 | if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1})) 241 | $proceed = true; 242 | } 243 | if($buffer == '' || $proceed) { 244 | $res = $this->say_punctuation($text, $escape_digits, $frequency); 245 | if($res['code'] == AGIRES_OK && $res['result'] > 0) { 246 | $buffer .= chr($res['result']); 247 | } 248 | return $res; 249 | } 250 | return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1})); 251 | } 252 | 253 | /** 254 | * Plays the given file and receives DTMF data. 255 | * Return early if $buffer is adequate for request. 256 | * 257 | * This is similar to STREAM FILE, but this command can accept and return many DTMF digits, 258 | * while STREAM FILE returns immediately after the first DTMF digit is detected. 259 | * 260 | * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default. 261 | * 262 | * If the user doesn't press any keys when the message plays, there is $timeout milliseconds 263 | * of silence then the command ends. 264 | * 265 | * The user has the opportunity to press a key at any time during the message or the 266 | * post-message silence. If the user presses a key while the message is playing, the 267 | * message stops playing. When the first key is pressed a timer starts counting for 268 | * $timeout milliseconds. Every time the user presses another key the timer is restarted. 269 | * The command ends when the counter goes to zero or the maximum number of digits is entered, 270 | * whichever happens first. 271 | * 272 | * If you don't specify a time out then a default timeout of 2000 is used following a pressed 273 | * digit. If no digits are pressed then 6 seconds of silence follow the message. 274 | * 275 | * If you don't specify $max_digits then the user can enter as many digits as they want. 276 | * 277 | * Pressing the # key has the same effect as the timer running out: the command ends and 278 | * any previously keyed digits are returned. A side effect of this is that there is no 279 | * way to read a # key using this command. 280 | * 281 | * @link http://www.voip-info.org/wiki-get+data 282 | * @param string $buffer 283 | * @param string $filename file to play. Do not include file extension. 284 | * @param integer $timeout milliseconds 285 | * @param integer $max_digits 286 | * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present. 287 | * 288 | * This differs from other commands with return DTMF as numbers representing ASCII characters. 289 | */ 290 | function fastpass_get_data(&$buffer, $filename, $timeout=NULL, $max_digits=NULL) { 291 | if(is_null($max_digits) || strlen($buffer) < $max_digits) { 292 | if($buffer == '') { 293 | $res = $this->get_data($filename, $timeout, $max_digits); 294 | if($res['code'] == AGIRES_OK) 295 | $buffer .= $res['result']; 296 | return $res; 297 | } else { 298 | while(is_null($max_digits) || strlen($buffer) < $max_digits) { 299 | $res = $this->wait_for_digit(); 300 | if($res['code'] != AGIRES_OK) { 301 | return $res; 302 | } 303 | if($res['result'] == ord('#')) { 304 | break; 305 | } 306 | $buffer .= chr($res['result']); 307 | } 308 | } 309 | } 310 | return array('code'=>AGIRES_OK, 'result'=>$buffer); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /manager.php: -------------------------------------------------------------------------------- 1 | , David Eder 7 | * Copyright (c) 2005 - 2015 Schmooze Com, Inc 8 | * All Rights Reserved. 9 | * 10 | * This software is released under the terms of the GNU Public License v2 11 | * A copy of which is available from http://www.fsf.org/licenses/gpl.txt 12 | * 13 | * @package phpAGI 14 | */ 15 | 16 | /** 17 | * Asterisk Manager class 18 | * 19 | * @link http://www.voip-info.org/wiki-Asterisk+config+manager.conf 20 | * @link http://www.voip-info.org/wiki-Asterisk+manager+API 21 | * @example examples/sip_show_peer.php Get information about a sip peer 22 | * @package phpAGI 23 | */ 24 | namespace Asterisk\AMI; 25 | class Manager { 26 | /** 27 | * Config variables 28 | * 29 | * @var array 30 | */ 31 | public $config; 32 | 33 | /** 34 | * Socket 35 | * 36 | */ 37 | public $socket = NULL; 38 | 39 | /** 40 | * Server we are connected to 41 | * 42 | * @var string 43 | */ 44 | public $server; 45 | 46 | /** 47 | * Port on the server we are connected to 48 | * 49 | * @var integer 50 | */ 51 | public $port; 52 | 53 | /** 54 | * Parent AGI 55 | * 56 | * @var AGI 57 | */ 58 | private $pagi; 59 | 60 | /** 61 | * Username we connected with (for reconnect) 62 | * 63 | * @var string 64 | */ 65 | public $username = NULL; 66 | 67 | /** 68 | * Secret we connected with (for reconnect) 69 | * 70 | * @var string 71 | */ 72 | public $secret = NULL; 73 | 74 | /** 75 | * Current state of events (for reconnect) 76 | * 77 | * @var string 78 | */ 79 | public $events = NULL; 80 | 81 | /** 82 | * Number of reconnect attempts per incident 83 | * 84 | * @var string 85 | */ 86 | public $reconnects = 2; 87 | 88 | /** 89 | * Asterisk settings from CoreSettings 90 | * @var array 91 | */ 92 | private $settings = null; 93 | 94 | /** 95 | * Event Handlers 96 | * 97 | * @var array 98 | */ 99 | private $event_handlers; 100 | 101 | /** 102 | * Log Level 103 | * 104 | * @var integer 105 | */ 106 | private $log_level; 107 | 108 | /** 109 | * Whether to cache the asterisk DB information 110 | * @var bool 111 | */ 112 | private $useCaching = false; 113 | 114 | /** 115 | * The cached Asterisk DB 116 | * @var array 117 | */ 118 | private $memAstDB = null; 119 | 120 | /** 121 | * Constructor 122 | * 123 | * @param string $config is an array of configuration vars and vals, stuffed into $this->config 124 | */ 125 | public function __construct($config=array()) { 126 | //No Errors to the screen 127 | //error_reporting(0); 128 | //@ini_set('display_errors', 0); 129 | 130 | // load config 131 | if(!empty($config) && is_array($config)) { 132 | $this->config = $config; 133 | } elseif(defined("DEFAULT_PHPAGI_CONFIG") && file_exists(DEFAULT_PHPAGI_CONFIG)) { 134 | $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true); 135 | } else { 136 | throw new \Exception("Asterisk Manager Config not found. Aborting"); 137 | } 138 | 139 | // add default values to config for uninitialized values 140 | if (!isset($this->config['server'])) { 141 | $this->config['server'] = 'localhost'; 142 | } 143 | if (!isset($this->config['port'])) { 144 | $this->config['port'] = 5038; 145 | } 146 | if (!isset($this->config['username'])) { 147 | $this->config['username'] = 'phpagi'; 148 | } 149 | if (!isset($this->config['secret'])) { 150 | $this->config['secret'] = 'phpagi'; 151 | } 152 | if (!isset($this->config['timeout'])) { 153 | $this->config['timeout'] = 5; 154 | } 155 | if (isset($this->config['cachemode'])) { 156 | $this->useCaching = $this->config['cachemode']; 157 | } 158 | 159 | $this->log_level = (isset($this->config['log_level']) && is_numeric($this->config['log_level'])) ? $this->config['log_level'] : false; 160 | $this->reconnects = isset($this->config['reconnects']) ? $this->config['reconnects'] : 2; 161 | } 162 | 163 | /** 164 | * Load Asterisk DB into local cache 165 | */ 166 | private function LoadAstDB() { 167 | if ($this->memAstDB != null) { 168 | unset($this->memAstDB); 169 | } 170 | $this->memAstDB = $this->database_show(); 171 | } 172 | 173 | /** 174 | * Send a request 175 | * 176 | * @param string $action 177 | * @param array $parameters 178 | * @return array of parameters 179 | */ 180 | public function send_request($action, $parameters=array(), $retry=true) { 181 | $reconnects = $this->reconnects; 182 | 183 | $req = "Action: $action\r\n"; 184 | foreach($parameters as $var=>$val) { 185 | if (is_array($val)) { 186 | foreach($val as $k => $v) { 187 | $req .= "$var: $k=$v\r\n"; 188 | } 189 | } else { 190 | $req .= "$var: $val\r\n"; 191 | } 192 | 193 | } 194 | $req .= "\r\n"; 195 | $this->log("Sending Request down socket:",10); 196 | $this->log($req,10); 197 | if(!$this->connected()) { 198 | throw new Exception("Asterisk is not connected"); 199 | } 200 | fwrite($this->socket, $req); 201 | $response = $this->wait_response(); 202 | 203 | // If we got a false back then something went wrong, we will try to reconnect the manager connection to try again 204 | // 205 | while ($response === false && $retry && $reconnects > 0) { 206 | $this->log("Unexpected failure executing command: $action, reconnecting to manager and retrying: $reconnects"); 207 | $this->disconnect(); 208 | if ($this->connect($this->server.':'.$this->port, $this->username, $this->secret, $this->events) !== false) { 209 | if(!$this->connected()) { 210 | throw new Exception("Asterisk is not connected"); 211 | } 212 | fwrite($this->socket, $req); 213 | $response = $this->wait_response(); 214 | } else { 215 | if ($reconnects > 1) { 216 | $this->log("reconnect command failed, sleeping before next attempt"); 217 | sleep(1); 218 | } else { 219 | $this->log("FATAL: no reconnect attempts left, command permanently failed, returning to calling program with 'false' failure code"); 220 | } 221 | } 222 | $reconnects--; 223 | } 224 | if($action == 'Command' && empty($response['data']) && !empty($response['Output'])) { 225 | $response['data'] = $response['Output']; 226 | unset($response['Output']); 227 | } 228 | return $response; 229 | } 230 | 231 | /** 232 | * Wait for a response 233 | * 234 | * If a request was just sent, this will return the response. 235 | * Otherwise, it will loop forever, handling events. 236 | * 237 | * @param boolean $allow_timeout if the socket times out, return an empty array 238 | * @return array of parameters, empty on timeout 239 | */ 240 | public function wait_response($allow_timeout = false) { 241 | $timeout = false; 242 | set_error_handler("Asterisk\AMI\phpasmanager_error_handler"); 243 | do { 244 | $type = NULL; 245 | $parameters = array(); 246 | 247 | if (!$this->socket || feof($this->socket)) { 248 | $this->log("Got EOF in wait_response() from socket waiting for response, returning false",10); 249 | restore_error_handler(); 250 | return false; 251 | } 252 | $buffer = trim(fgets($this->socket, 4096)); 253 | while($buffer != '') { 254 | $a = strpos($buffer, ':'); 255 | if($a) { 256 | if(!count($parameters)) {// first line in a response? 257 | $type = strtolower(substr($buffer, 0, $a)); 258 | if((substr($buffer, $a + 2) == 'Follows')) { 259 | // A 'follows' response means there is a muiltiline field that follows. 260 | $parameters['data'] = ''; 261 | $buff = fgets($this->socket, 4096); 262 | while(substr($buff, 0, 6) != '--END ') { 263 | $parameters['data'] .= $buff; 264 | $buff = fgets($this->socket, 4096); 265 | } 266 | } 267 | } elseif(count($parameters) == 2) { 268 | if($parameters['Response'] == "Success" && isset($parameters['Message']) && $parameters['Message'] == 'Command output follows') { 269 | // A 'Command output follows' response means there is a muiltiline field that follows. 270 | $parameters['data'] = ''; 271 | $buff = fgets($this->socket, 4096); 272 | while($buff !== "\r\n") { 273 | $buff = preg_replace("/^Output:\s*/","",$buff); 274 | $parameters['data'] .= $buff; 275 | $buff = fgets($this->socket, 4096); 276 | } 277 | if(empty($parameters['data'])) { 278 | $parameters['data'] = preg_replace("/^Output:\s*/","",$buffer); 279 | } 280 | break; 281 | } 282 | } 283 | 284 | // store parameter in $parameters 285 | $parameters[substr($buffer, 0, $a)] = substr($buffer, $a + 2); 286 | } 287 | $buffer = trim(fgets($this->socket, 4096)); 288 | } 289 | 290 | // process response 291 | switch($type) { 292 | case '': // timeout occured 293 | $timeout = $allow_timeout; 294 | break; 295 | case 'event': 296 | $this->process_event($parameters); 297 | break; 298 | case 'response': 299 | case 'message': 300 | break; 301 | default: 302 | $this->log('Unhandled response packet ('.$type.') from Manager: ' . print_r($parameters, true)); 303 | break; 304 | } 305 | } while($type != 'response' && $type != 'message' && !$timeout); 306 | 307 | $this->log("returning from wait_response with with type: $type",10); 308 | $this->log('$parmaters: '.print_r($parameters,true),10); 309 | $this->log('$buffer: '.print_r($buffer,true),10); 310 | if (isset($buff)) { 311 | $this->log('$buff: '.print_r($buff,true),10); 312 | } 313 | restore_error_handler(); 314 | return $parameters; 315 | } 316 | 317 | /** 318 | * Connect to Asterisk 319 | * 320 | * @example examples/sip_show_peer.php Get information about a sip peer 321 | * 322 | * @param string $server 323 | * @param string $username 324 | * @param string $secret 325 | * @return boolean true on success 326 | */ 327 | public function connect($server=NULL, $username=NULL, $secret=NULL, $events='on') { 328 | set_error_handler("Asterisk\AMI\phpasmanager_error_handler"); 329 | // use config if not specified 330 | if(is_null($server)) { 331 | $server = $this->config['server']; 332 | } 333 | $this->username = is_null($username) ? $this->config['username'] : $username; 334 | $this->secret = is_null($secret) ? $this->config['secret'] : $secret; 335 | $this->events = $events; 336 | 337 | // get port from server if specified 338 | if(strpos($server, ':') !== false) { 339 | $c = explode(':', $server); 340 | $this->server = $c[0]; 341 | $this->port = $c[1]; 342 | } else { 343 | $this->server = $server; 344 | $this->port = $this->config['port']; 345 | } 346 | 347 | // connect the socket 348 | $errno = $errstr = NULL; 349 | $this->socket = stream_socket_client("tcp://".$this->server.":".$this->port, $errno, $errstr); 350 | stream_set_timeout($this->socket,30); 351 | if(!$this->socket) { 352 | restore_error_handler(); 353 | throw new \Exception("Unable to connect to manager {$this->server}:{$this->port} ($errno): $errstr"); 354 | } 355 | 356 | // read the header 357 | $str = fgets($this->socket); 358 | if($str == false) { 359 | // a problem. 360 | restore_error_handler(); 361 | throw new \Exception("Asterisk Manager Header not received"); 362 | } else { 363 | // note: don't $this->log($str) until someone looks to see why it mangles the logging 364 | } 365 | 366 | stream_set_timeout($this->socket, $this->config['timeout']); 367 | 368 | // login 369 | $res = $this->send_request('login', 370 | array( 371 | 'Username'=>$this->username, 372 | 'Secret'=>$this->secret, 373 | 'Events'=>$this->events 374 | ), 375 | false); 376 | if($res['Response'] != 'Success') { 377 | $this->disconnect(); 378 | restore_error_handler(); 379 | throw new \Exception("Failed to login"); 380 | return false; 381 | } 382 | $this->CoreSettings(); 383 | restore_error_handler(); 384 | return true; 385 | } 386 | 387 | /** 388 | * Disconnect 389 | * 390 | */ 391 | public function disconnect() { 392 | if($this->connected()) { 393 | $this->logoff(); 394 | } 395 | fclose($this->socket); 396 | $this->settings = null; 397 | } 398 | 399 | /** 400 | * Check if the socket is connected 401 | * 402 | */ 403 | public function connected() { 404 | return is_resource($this->socket) && !feof($this->socket); 405 | } 406 | 407 | /** 408 | * Set Absolute Timeout 409 | * 410 | * Hangup a channel after a certain time. Acknowledges set time with Timeout Set message. 411 | * 412 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_AbsoluteTimeout 413 | * @version 11 414 | * @param string $channel 415 | * @param integer $timeout 416 | */ 417 | public function AbsoluteTimeout($channel, $timeout) { 418 | return $this->send_request('AbsoluteTimeout', array('Channel'=>$channel, 'Timeout'=>$timeout)); 419 | } 420 | 421 | /** 422 | * Show PBX core settings (version etc). 423 | * 424 | * Query for Core PBX settings. 425 | * 426 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_CoreSettings 427 | */ 428 | public function CoreSettings() { 429 | if(empty($this->settings)) { 430 | $this->settings = $this->send_request('CoreSettings'); 431 | } 432 | return $this->settings; 433 | } 434 | 435 | /** 436 | * Sets an agent as no longer logged in. 437 | * 438 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_AgentLogoff 439 | * @version 11 440 | * @param string $agent Agent ID of the agent to log off. 441 | * @param string $soft Set to true to not hangup existing calls. 442 | */ 443 | public function AgentLogoff($agent, $soft='false') { 444 | return $this->send_request('AgentLogoff', array('Agent'=>$agent, 'Soft'=>$soft)); 445 | } 446 | 447 | /** 448 | * Lists agents and their status. 449 | * 450 | * Will list info about all possible agents. 451 | * 452 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Agents 453 | * @version 11 454 | */ 455 | public function Agents() { 456 | return $this->send_request('Agents'); 457 | } 458 | 459 | /** 460 | * Add an AGI command to execute by Async AGI. 461 | * 462 | * Add an AGI command to the execute queue of the channel in Async AGI. 463 | * 464 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_AGI 465 | * @param string $channel Channel that is currently in Async AGI. 466 | * @param string $command Application to execute. 467 | * @param string $commandid This will be sent back in CommandID header of AsyncAGI exec event notification. 468 | */ 469 | public function AGI($channel, $command, $commandid) { 470 | return $this->send_request('AGI', array('Channel'=>$channel, 'Command'=>$command, "CommandID" => $commandid)); 471 | } 472 | 473 | /** 474 | * Send an arbitrary event. 475 | * 476 | * Send an event to manager sessions. 477 | * 478 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_UserEvent 479 | * @param string $channel 480 | * @param string $file 481 | */ 482 | function UserEvent($event, $headers=array()) { 483 | $d = array('UserEvent'=>$event); 484 | $i = 1; 485 | foreach($headers as $header) { 486 | $d['Header'.$i] = $header; 487 | $i++; 488 | } 489 | return $this->send_request('UserEvent', $d); 490 | } 491 | 492 | /** 493 | * Change monitoring filename of a channel 494 | * 495 | * This action may be used to change the file started by a previous 'Monitor' action. 496 | * 497 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ChangeMonitor 498 | * @param string $channel Used to specify the channel to record. 499 | * @param string $file Is the new name of the file created in the monitor spool directory. 500 | */ 501 | public function ChangeMonitor($channel, $file) { 502 | return $this->send_request('ChangeMonitor', array('Channel'=>$channel, 'File'=>$file)); 503 | } 504 | 505 | /** 506 | * Execute Asterisk CLI Command 507 | * 508 | * Run a CLI command 509 | * 510 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Command 511 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+Command+Line+Interface 512 | * @param string $command Asterisk CLI command to run 513 | * @param string $actionid message matching variable 514 | */ 515 | public function Command($command, $actionid=NULL) { 516 | $parameters = array('Command'=>$command); 517 | if($actionid) { 518 | $parameters['ActionID'] = $actionid; 519 | } 520 | return $this->send_request('Command', $parameters); 521 | } 522 | 523 | /** 524 | * Tell Asterisk to poll mailboxes for a change 525 | * 526 | * Normally, MWI indicators are only sent when Asterisk itself changes a mailbox. 527 | * With external programs that modify the content of a mailbox from outside the 528 | * application, an option exists called pollmailboxes that will cause voicemail 529 | * to continually scan all mailboxes on a system for changes. This can cause a 530 | * large amount of load on a system. This command allows external applications 531 | * to signal when a particular mailbox has changed, thus permitting external 532 | * applications to modify mailboxes and MWI to work without introducing 533 | * considerable CPU load. 534 | * 535 | * If Context is not specified, all mailboxes on the system will be polled for 536 | * changes. If Context is specified, but Mailbox is omitted, then all mailboxes 537 | * within Context will be polled. Otherwise, only a single mailbox will be 538 | * polled for changes. 539 | * 540 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+12+ManagerAction_VoicemailRefresh 541 | * @param string $context 542 | * @param string $mailbox 543 | * @param string $actionid ActionID for this transaction. Will be returned. 544 | */ 545 | function VoicemailRefresh($context=NULL,$mailbox=NULL, $actionid=NULL) { 546 | if(version_compare($this->settings['AsteriskVersion'], "12.0", "lt")) { 547 | return false; 548 | } 549 | $parameters = array(); 550 | if(!empty($context)) { 551 | $parameters['Context'] = $context; 552 | } 553 | if(!empty($mailbox)) { 554 | $parameters['Mailbox'] = $mailbox; 555 | } 556 | if(!empty($actionid)) { 557 | $parameters['ActionID'] = $actionid; 558 | } 559 | return $this->send_request('VoicemailRefresh', $parameters); 560 | } 561 | 562 | /** 563 | * Get and parse codecs 564 | * @param {string} $type='audio' Type of codec to look up 565 | */ 566 | public function Codecs($type='audio') { 567 | $type = strtolower($type); 568 | switch($type) { 569 | case 'video': 570 | $ret = $this->Command('core show codecs video'); 571 | break; 572 | case 'text': 573 | $ret = $this->Command('core show codecs text'); 574 | break; 575 | case 'image': 576 | $ret = $this->Command('core show codecs image'); 577 | break; 578 | case 'audio': 579 | default: 580 | $ret = $this->Command('core show codecs audio'); 581 | break; 582 | } 583 | 584 | if(preg_match_all('/\d{1,6}\s*'.$type.'\s*([a-z0-9]*)\s/i',$ret['data'],$matches)) { 585 | return $matches[1]; 586 | } else { 587 | return array(); 588 | } 589 | } 590 | 591 | /** 592 | * Kick a Confbridge user. 593 | * 594 | * Kick a Confbridge user. 595 | * 596 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeKick 597 | * @param string $conference Conference number. 598 | * @param string $channel If this parameter is not a complete channel name, the first channel with this prefix will be used. 599 | */ 600 | public function ConfbridgeKick($conference, $channel) { 601 | return $this->send_request('ConfbridgeKick', array('Conference'=>$conference, 'Channel'=>$channel)); 602 | } 603 | 604 | /** 605 | * List Users in a Conference 606 | * 607 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeList 608 | * @param string $conference Conference number. 609 | */ 610 | public function ConfbridgeList($conference) { 611 | $this->add_event_handler("confbridgelist", array($this, 'Confbridge_catch')); 612 | $this->add_event_handler("confbridgelistcomplete", array($this, 'Confbridge_catch')); 613 | $response = $this->send_request('ConfbridgeList', array('Conference'=>$conference)); 614 | if ($response["Response"] == "Success") { 615 | $this->response_catch = array(); 616 | $this->wait_response(true); 617 | stream_set_timeout($this->socket, 30); 618 | } else { 619 | return false; 620 | } 621 | return $this->response_catch; 622 | } 623 | 624 | /** 625 | * List active conferences. 626 | * 627 | * Lists data about all active conferences. ConfbridgeListRooms will follow as separate events, followed by a final event called ConfbridgeListRoomsComplete. 628 | * 629 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeListRooms 630 | */ 631 | public function ConfbridgeListRooms() { 632 | $this->add_event_handler("confbridgelistrooms", array($this, 'Confbridge_catch')); 633 | $this->add_event_handler("confbridgelistroomscomplete", array($this, 'Confbridge_catch')); 634 | $response = $this->send_request('ConfbridgeListRooms'); 635 | if ($response["Response"] == "Success") { 636 | $this->response_catch = array(); 637 | $this->wait_response(true); 638 | stream_set_timeout($this->socket, 30); 639 | } else { 640 | return false; 641 | } 642 | return $this->response_catch; 643 | } 644 | 645 | /** 646 | * Conference Bridge Event Catch 647 | * 648 | * This catches events obtained from the confbridge stream, it should not be used externally 649 | * 650 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeListRooms 651 | */ 652 | private function Confbridge_catch($event, $data, $server, $port) { 653 | switch($event) { 654 | case 'confbridgelistcomplete': 655 | case 'confbridgelistroomscomplete': 656 | /* HACK: Force a timeout after we get this event, so that the wait_response() returns. */ 657 | stream_set_timeout($this->socket, 0, 1); 658 | break; 659 | case 'confbridgelist': 660 | $this->response_catch[] = $data; 661 | break; 662 | case 'confbridgelistrooms': 663 | $this->response_catch[] = $data; 664 | break; 665 | } 666 | } 667 | 668 | /** 669 | * Conference Bridge Event Catch 670 | * 671 | * This catches events obtained from the confbridge stream, it should not be used externally 672 | * 673 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeListRooms 674 | */ 675 | private function Meetme_catch($event, $data, $server, $port) { 676 | switch($event) { 677 | case 'meetmelistcomplete': 678 | case 'meetmelistroomscomplete': 679 | /* HACK: Force a timeout after we get this event, so that the wait_response() returns. */ 680 | stream_set_timeout($this->socket, 0, 1); 681 | break; 682 | case 'meetmelist': 683 | $this->response_catch[] = $data; 684 | break; 685 | case 'meetmelistrooms': 686 | $this->response_catch[] = $data; 687 | break; 688 | } 689 | } 690 | 691 | /** 692 | * Lock a Confbridge conference. 693 | * 694 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeLock 695 | * @param string $conference Conference number. 696 | */ 697 | function ConfbridgeLock($conference) { 698 | return $this->send_request('ConfbridgeLock', array('Conference'=>$conference)); 699 | } 700 | 701 | /** 702 | * Mute a Confbridge user. 703 | * 704 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeMute 705 | * @param string $conference Conference number. 706 | * @param string $channel If this parameter is not a complete channel name, the first channel with this prefix will be used. 707 | */ 708 | function ConfbridgeMute($conference,$channel) { 709 | return $this->send_request('ConfbridgeMute', array('Conference'=>$conference, 'Channel' => $channel)); 710 | } 711 | 712 | /** 713 | * Set a conference user as the single video source distributed to all other participants. 714 | * 715 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeSetSingleVideoSrc 716 | * @param string $conference Conference number. 717 | * @param string $channel If this parameter is not a complete channel name, the first channel with this prefix will be used. 718 | */ 719 | function ConfbridgeSetSingleVideoSrc($conference,$channel) { 720 | return $this->send_request('ConfbridgeSetSingleVideoSrc', array('Conference'=>$conference, 'Channel' => $channel)); 721 | } 722 | 723 | /** 724 | * Start recording a Confbridge conference. 725 | * 726 | * Start recording a conference. If recording is already present an error will be returned. 727 | * If RecordFile is not provided, the default record file specified in the conference's bridge profile will be used, if that is not present either a file will automatically be generated in the monitor directory. 728 | * 729 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeMute 730 | * @param string $conference Conference number. 731 | * @param string $channel If this parameter is not a complete channel name, the first channel with this prefix will be used. 732 | */ 733 | function ConfbridgeStartRecord($conference,$recordFile) { 734 | return $this->send_request('ConfbridgeStartRecord', array('Conference'=>$conference, 'RecordFile' => $recordFile)); 735 | } 736 | 737 | /** 738 | * Stop recording a Confbridge conference. 739 | * 740 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeStopRecord 741 | * @param string $conference Conference number. 742 | */ 743 | function ConfbridgeStopRecord($conference) { 744 | return $this->send_request('ConfbridgeStopRecord', array('Conference'=>$conference)); 745 | } 746 | 747 | /** 748 | * Unlock a Confbridge conference. 749 | * 750 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeUnlock 751 | * @param string $conference Conference number. 752 | */ 753 | function ConfbridgeUnlock($conference) { 754 | return $this->send_request('ConfbridgeUnlock', array('Conference'=>$conference)); 755 | } 756 | 757 | /** 758 | * Unmute a Confbridge user. 759 | * 760 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeUnmute 761 | * @param string $conference Conference number. 762 | */ 763 | function ConfbridgeUnmute($conference,$channel) { 764 | return $this->send_request('ConfbridgeUnmute', array('Conference'=>$conference,'Channel' => $channel)); 765 | } 766 | 767 | /** 768 | * Enable/Disable sending of events to this manager 769 | * 770 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Events 771 | * @param string $eventmask is either 'on', 'off', or 'system,call,log' 772 | */ 773 | function Events($eventmask) { 774 | $this->events = $eventmask; 775 | return $this->send_request('Events', array('EventMask'=>$eventmask)); 776 | } 777 | 778 | /** 779 | * Check Extension Status 780 | * 781 | * Report the extension state for given extension. 782 | * If the extension has a hint, will use devicestate to check the status of the device connected to the extension. 783 | * Will return an Extension Status message. 784 | * The response will include the hint for the extension and the status. 785 | * 786 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ExtensionState 787 | * @param string $exten Extension to check state on 788 | * @param string $context Context for extension 789 | * @param string $actionid message matching variable 790 | */ 791 | function ExtensionState($exten, $context, $actionid = NULL) { 792 | $parameters = array('Exten'=>$exten, 'Context'=>$context); 793 | if($actionid) { 794 | $parameters['ActionID'] = $actionid; 795 | } 796 | return $this->send_request('ExtensionState', $parameters); 797 | } 798 | 799 | /** 800 | * Gets a Channel Variable 801 | * 802 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Getvar 803 | * @param string $channel Channel to read variable from 804 | * @param string $variable Variable name, function or expression 805 | * @param string $actionid message matching variable 806 | */ 807 | function GetVar($channel, $variable, $actionid=NULL) { 808 | $parameters = array('Channel'=>$channel, 'Variable'=>$variable); 809 | if($actionid) { 810 | $parameters['ActionID'] = $actionid; 811 | } 812 | return $this->send_request('GetVar', $parameters); 813 | } 814 | 815 | /** 816 | * Hangup Channel 817 | * 818 | * @link https://wiki.asterisk.org/wiki/display/AST/ManagerAction_Hangup 819 | * @param string $channel The channel name to be hungup 820 | */ 821 | function Hangup($channel) { 822 | return $this->send_request('Hangup', array('Channel'=>$channel)); 823 | } 824 | 825 | /** 826 | * List IAX Peers 827 | * 828 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_IAXpeers 829 | */ 830 | function IAXPeers() { 831 | return $this->send_request('IAXPeers'); 832 | } 833 | 834 | /** 835 | * Check Presence State 836 | * 837 | * Report the presence state for the given presence provider. 838 | * Will return a Presence State message. 839 | * The response will include the presence state and, if set, a presence subtype and custom message. 840 | * 841 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+ManagerAction_PresenceState 842 | * @param string $provider Presence Provider to check the state of 843 | */ 844 | function PresenceState($provider) { 845 | return $this->send_request('PresenceState',array('Provider'=>$provider)); 846 | } 847 | 848 | /** 849 | * List available manager commands 850 | * 851 | * Returns the action name and synopsis for every action that is available to the user. 852 | * 853 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ListCommands 854 | * @param string $actionid message matching variable 855 | */ 856 | function ListCommands($actionid=NULL) { 857 | if($actionid) { 858 | return $this->send_request('ListCommands', array('ActionID'=>$actionid)); 859 | } else { 860 | return $this->send_request('ListCommands'); 861 | } 862 | } 863 | 864 | /** 865 | * Logoff Manager 866 | * 867 | * Logoff the current manager session. 868 | * 869 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Logoff 870 | */ 871 | function Logoff() { 872 | return $this->send_request('Logoff',array(),false); 873 | } 874 | 875 | /** 876 | * Check Mailbox Message Count 877 | * 878 | * Returns number of new and old messages. 879 | * Message: Mailbox Message Count 880 | * Mailbox: 881 | * NewMessages: 882 | * OldMessages: 883 | * 884 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_MailboxStatus 885 | * @param string $mailbox Full mailbox ID @ 886 | */ 887 | function MailboxCount($mailbox, $actionid=NULL) { 888 | $parameters = array('Mailbox'=>$mailbox); 889 | if($actionid) { 890 | $parameters['ActionID'] = $actionid; 891 | } 892 | return $this->send_request('MailboxCount', $parameters); 893 | } 894 | 895 | /** 896 | * Check Mailbox 897 | * 898 | * Returns number of messages. 899 | * Message: Mailbox Status 900 | * Mailbox: 901 | * Waiting: 902 | * 903 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxStatus 904 | * @param string $mailbox Full mailbox ID @ 905 | * @param string $actionid message matching variable 906 | */ 907 | function MailboxStatus($mailbox, $actionid=NULL) { 908 | $parameters = array('Mailbox'=>$mailbox); 909 | if($actionid) { 910 | $parameters['ActionID'] = $actionid; 911 | } 912 | return $this->send_request('MailboxStatus', $parameters); 913 | } 914 | 915 | /** 916 | * MessageSend 917 | * 918 | * Send an SMS message 919 | * 920 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_MessageSend 921 | * @param string $to 922 | * @param string $from 923 | * @param string $body 924 | * @param string $variable optional 925 | * @return array result of send_request 926 | */ 927 | function MessageSend($to, $from, $body, $variable=null) { 928 | $parameters['To'] = $to; 929 | $parameters['From'] = $from; 930 | $parameters['Base64Body'] = base64_encode($body); 931 | if($variable) { 932 | $parameters['Variable'] = $variable; 933 | } 934 | return $this->send_request('MessageSend', $parameters); 935 | } 936 | 937 | /** 938 | * List participants in a conference. 939 | * 940 | * Lists all users in a particular MeetMe conference. MeetmeList will follow as separate events, followed by a final event called MeetmeListComplete. 941 | * 942 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_MeetmeList 943 | * @param string $conference Conference number. 944 | */ 945 | function MeetmeList($conference) { 946 | $this->add_event_handler("meetmelist", array($this, 'Meetme_catch')); 947 | $this->add_event_handler("meetmelistcomplete", array($this, 'Meetme_catch')); 948 | $response = $this->send_request('MeetmeList', array('Conference'=>$conference)); 949 | if ($response["Response"] == "Success") { 950 | $this->response_catch = array(); 951 | $this->wait_response(true); 952 | stream_set_timeout($this->socket, 30); 953 | } else { 954 | return false; 955 | } 956 | return $this->response_catch; 957 | } 958 | 959 | /** 960 | * List active conferences. 961 | * 962 | * Lists data about all active conferences. MeetmeListRooms will follow as separate events, followed by a final event called MeetmeListRoomsComplete. 963 | * 964 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ConfbridgeListRooms 965 | */ 966 | public function MeetmeListRooms() { 967 | $this->add_event_handler("meetmelistrooms", array($this, 'Meetme_catch')); 968 | $this->add_event_handler("meetmelistroomscomplete", array($this, 'Meetme_catch')); 969 | $response = $this->send_request('MeetmeListRooms'); 970 | if ($response["Response"] == "Success") { 971 | $this->response_catch = array(); 972 | $this->wait_response(true); 973 | stream_set_timeout($this->socket, 30); 974 | } else { 975 | return false; 976 | } 977 | return $this->response_catch; 978 | } 979 | 980 | /** 981 | * Mute a Meetme user. 982 | * 983 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_MeetmeUnmute 984 | * @param string $meetme Conference number. 985 | * @param string $usernum User Number 986 | */ 987 | public function MeetmeMute($meetme,$usernum) { 988 | return $this->send_request('MeetmeMute', array('Meetme'=>$meetme,'Usernum'=>$usernum)); 989 | } 990 | 991 | /** 992 | * Unmute a Meetme user. 993 | * 994 | * Unmute a Meetme user. 995 | * 996 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_MeetmeUnmute 997 | * @param string $meetme Conference number. 998 | * @param string $usernum User Number 999 | */ 1000 | public function MeetmeUnmute($meetme,$usernum) { 1001 | return $this->send_request('MeetmeUnmute', array('Meetme'=>$meetme,'Usernum'=>$usernum)); 1002 | } 1003 | 1004 | /** 1005 | * Monitor a channel 1006 | * 1007 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Monitor 1008 | * @param string $channel 1009 | * @param string $file 1010 | * @param string $format 1011 | * @param boolean $mix 1012 | */ 1013 | public function Monitor($channel, $file=NULL, $format=NULL, $mix=NULL) { 1014 | $parameters = array('Channel'=>$channel); 1015 | if($file) { 1016 | $parameters['File'] = $file; 1017 | } 1018 | if($format) { 1019 | $parameters['Format'] = $format; 1020 | } 1021 | if(!is_null($file)) { 1022 | $parameters['Mix'] = ($mix) ? 'true' : 'false'; 1023 | } 1024 | return $this->send_request('Monitor', $parameters); 1025 | } 1026 | 1027 | /** 1028 | * Originate Call 1029 | * 1030 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Originate 1031 | * @param string $channel 1032 | * @param string $exten 1033 | * @param string $context 1034 | * @param string $priority 1035 | * @param integer $timeout 1036 | * @param string $callerid 1037 | * @param string $variable (Supports an array of values) 1038 | * @param string $account 1039 | * @param string $application 1040 | * @param string $data 1041 | * == exactly 11 values required == 1042 | * 1043 | * -- OR -- 1044 | * 1045 | * @pram array a key => value array of what ever you want to pass in 1046 | */ 1047 | public function Originate() { 1048 | $num_args = func_num_args(); 1049 | 1050 | if ($num_args === 10) { 1051 | $args = func_get_args(); 1052 | 1053 | $parameters = array(); 1054 | if ($args[0]) { 1055 | $parameters['Channel'] = $args[0]; 1056 | } 1057 | if ($args[1]) { 1058 | $parameters['Exten'] = $args[1]; 1059 | } 1060 | if ($args[2]) { 1061 | $parameters['Context'] = $args[2]; 1062 | } 1063 | if ($args[3]) { 1064 | $parameters['Priority'] = $args[3]; 1065 | } 1066 | if ($args[4]) { 1067 | $parameters['Timeout'] = $args[4]; 1068 | } 1069 | if ($args[5]) { 1070 | $parameters['CallerID'] = $args[5]; 1071 | } 1072 | if ($args[6]) { 1073 | $parameters['Variable'] = $args[6]; 1074 | } 1075 | if ($args[7]) { 1076 | $parameters['Account'] = $args[7]; 1077 | } 1078 | if ($args[8]) { 1079 | $parameters['Application'] = $args[8]; 1080 | } 1081 | if ($args[9]) { 1082 | $parameters['Data'] = $args[9]; 1083 | } 1084 | } else { 1085 | $args = func_get_args(); 1086 | $args = $args[0]; 1087 | foreach ($args as $key => $val) { 1088 | $parameters[$key] = $val; 1089 | } 1090 | } 1091 | 1092 | return $this->send_request('Originate', $parameters); 1093 | } 1094 | 1095 | /** 1096 | * List parked calls 1097 | * 1098 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_ParkedCalls 1099 | */ 1100 | public function ParkedCalls($actionid=NULL) { 1101 | if($actionid) { 1102 | return $this->send_request('ParkedCalls', array('ActionID'=>$actionid)); 1103 | } else { 1104 | return $this->send_request('ParkedCalls'); 1105 | } 1106 | } 1107 | 1108 | /** 1109 | * Ping 1110 | * 1111 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Ping 1112 | */ 1113 | public function Ping() { 1114 | return $this->send_request('Ping'); 1115 | } 1116 | 1117 | /** 1118 | * Queue Add 1119 | * 1120 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_QueueAdd 1121 | * @param string $queue 1122 | * @param string $interface 1123 | * @param integer $penalty 1124 | */ 1125 | public function QueueAdd($queue, $interface, $penalty=0) { 1126 | $parameters = array('Queue'=>$queue, 'Interface'=>$interface); 1127 | if($penalty) { 1128 | $parameters['Penalty'] = $penalty; 1129 | } 1130 | return $this->send_request('QueueAdd', $parameters); 1131 | } 1132 | 1133 | /** 1134 | * Queue Remove 1135 | * 1136 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_QueueRemove 1137 | * @param string $queue 1138 | * @param string $interface 1139 | */ 1140 | public function QueueRemove($queue, $interface) { 1141 | return $this->send_request('QueueRemove', array('Queue'=>$queue, 'Interface'=>$interface)); 1142 | } 1143 | /** 1144 | * Queues 1145 | * 1146 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Queues 1147 | */ 1148 | public function Queues() { 1149 | return $this->send_request('Queues'); 1150 | } 1151 | 1152 | /** 1153 | * Queue Status 1154 | * 1155 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_QueueStatus 1156 | * @param string $actionid message matching variable 1157 | */ 1158 | public function QueueStatus($actionid=NULL) { 1159 | if($actionid) { 1160 | return $this->send_request('QueueStatus', array('ActionID'=>$actionid)); 1161 | } else { 1162 | return $this->send_request('QueueStatus'); 1163 | } 1164 | } 1165 | 1166 | /** 1167 | * Redirect 1168 | * 1169 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Redirect 1170 | * @param string $channel 1171 | * @param string $extrachannel 1172 | * @param string $exten 1173 | * @param string $context 1174 | * @param string $priority 1175 | */ 1176 | public function Redirect($channel, $extrachannel, $exten, $context, $priority) { 1177 | return $this->send_request('Redirect', array('Channel'=>$channel, 'ExtraChannel'=>$extrachannel, 'Exten'=>$exten, 'Context'=>$context, 'Priority'=>$priority)); 1178 | } 1179 | 1180 | /** 1181 | * Set the CDR UserField 1182 | * 1183 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetCDRUserField 1184 | * @param string $userfield 1185 | * @param string $channel 1186 | * @param string $append 1187 | */ 1188 | public function SetCDRUserField($userfield, $channel, $append=NULL) { 1189 | $parameters = array('UserField'=>$userfield, 'Channel'=>$channel); 1190 | if($append) { 1191 | $parameters['Append'] = $append; 1192 | } 1193 | return $this->send_request('SetCDRUserField', $parameters); 1194 | } 1195 | 1196 | /** 1197 | * Set Channel Variable 1198 | * 1199 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Setvar 1200 | * @param string $channel Channel to set variable for 1201 | * @param string $variable name 1202 | * @param string $value 1203 | */ 1204 | public function SetVar($channel, $variable, $value) { 1205 | return $this->send_request('SetVar', array('Channel'=>$channel, 'Variable'=>$variable, 'Value'=>$value)); 1206 | } 1207 | 1208 | /** 1209 | * List SIP Peers 1210 | */ 1211 | public function SIPpeers() { 1212 | //TODO need to look at source to find this function... 1213 | return $this->send_request('SIPpeers'); 1214 | } 1215 | 1216 | /** 1217 | * Channel Status 1218 | * 1219 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Status 1220 | * @param string $channel 1221 | * @param string $actionid message matching variable 1222 | */ 1223 | public function Status($channel, $actionid=NULL) { 1224 | $parameters = array('Channel'=>$channel); 1225 | if ($actionid) { 1226 | $parameters['ActionID'] = $actionid; 1227 | } 1228 | return $this->send_request('Status', $parameters); 1229 | } 1230 | 1231 | /** 1232 | * Stop monitoring a channel 1233 | * 1234 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_StopMonitor 1235 | * @param string $channel 1236 | */ 1237 | public function StopMonitor($channel) { 1238 | return $this->send_request('StopMonitor', array('Channel'=>$channel)); 1239 | } 1240 | 1241 | /** 1242 | * Dial over Zap channel while offhook 1243 | * 1244 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDialOffhook 1245 | * @param string $zapchannel 1246 | * @param string $number 1247 | */ 1248 | public function ZapDialOffhook($zapchannel = '', $number = '') { 1249 | //TODO: need to look at source to find this function... 1250 | if ($zapchannel && $number) { 1251 | return $this->send_request('ZapDialOffhook', array('ZapChannel'=>$zapchannel, 'Number'=>$number)); 1252 | } else { 1253 | return $this->send_request('ZapDialOffhook'); 1254 | } 1255 | } 1256 | 1257 | /** 1258 | * Toggle Zap channel Do Not Disturb status OFF 1259 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDoff 1260 | * @param string $zapchannel 1261 | */ 1262 | public function ZapDNDoff($zapchannel = '') { 1263 | //TODO: need to look at source to find this function... 1264 | if ($zapchannel) { 1265 | return $this->send_request('ZapDNDoff', array('ZapChannel'=>$zapchannel)); 1266 | } else { 1267 | return $this->send_request('ZapDNDoff'); 1268 | } 1269 | } 1270 | 1271 | /** 1272 | * Toggle Zap channel Do Not Disturb status ON 1273 | * 1274 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapDNDon 1275 | * @param string $zapchannel 1276 | */ 1277 | public function ZapDNDon($zapchannel = '') { 1278 | //TODO: need to look at source to find this function... 1279 | if ($zapchannel) { 1280 | return $this->send_request('ZapDNDon', array('ZapChannel'=>$zapchannel)); 1281 | } else { 1282 | return $this->send_request('ZapDNDon'); 1283 | } 1284 | } 1285 | 1286 | /** 1287 | * Hangup Zap Channel 1288 | * 1289 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapHangup 1290 | * @param string $zapchannel 1291 | */ 1292 | public function ZapHangup($zapchannel = '') { 1293 | //TODO: need to look at source to find this function... 1294 | if ($zapchannel) { 1295 | return $this->send_request('ZapHangup', array('ZapChannel'=>$zapchannel)); 1296 | } else { 1297 | return $this->send_request('ZapHangup'); 1298 | } 1299 | } 1300 | 1301 | /** 1302 | * Transfer Zap Channel 1303 | * 1304 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapTransfer 1305 | * @param string $zapchannel 1306 | */ 1307 | public function ZapTransfer($zapchannel = '') { 1308 | //TODO need to look at source to find this function... 1309 | if ($zapchannel) { 1310 | return $this->send_request('ZapTransfer', array('ZapChannel'=>$zapchannel)); 1311 | } else { 1312 | return $this->send_request('ZapTransfer'); 1313 | } 1314 | } 1315 | 1316 | /** 1317 | * Zap Show Channels 1318 | * 1319 | * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ZapShowChannels 1320 | * @param string $actionid message matching variable 1321 | */ 1322 | function ZapShowChannels($actionid=NULL) { 1323 | if($actionid) { 1324 | return $this->send_request('ZapShowChannels', array('ActionID'=>$actionid)); 1325 | } else { 1326 | return $this->send_request('ZapShowChannels'); 1327 | } 1328 | } 1329 | 1330 | /** 1331 | * Log a message 1332 | * 1333 | * @param string $message 1334 | * @param integer $level from 1 to 4 1335 | */ 1336 | public function log($message, $level = 1) { 1337 | if($this->pagi != false) { 1338 | $this->pagi->conlog($message, $level); 1339 | } elseif ($this->log_level === false && $level <= $this->log_level) { 1340 | error_log(date('r') . ' - ' . $message); 1341 | } 1342 | } 1343 | 1344 | /** 1345 | * Add event handler 1346 | * 1347 | * Known Events include ( https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AMI+Events ) 1348 | * Link - Fired when two voice channels are linked together and voice data exchange commences. 1349 | * Unlink - Fired when a link between two voice channels is discontinued, for example, just before call completion. 1350 | * Newexten - 1351 | * Hangup - 1352 | * Newchannel - 1353 | * Newstate - 1354 | * Reload - Fired when the "RELOAD" console command is executed. 1355 | * Shutdown - 1356 | * ExtensionStatus - 1357 | * Rename - 1358 | * Newcallerid - 1359 | * Alarm - 1360 | * AlarmClear - 1361 | * Agentcallbacklogoff - 1362 | * Agentcallbacklogin - 1363 | * Agentlogoff - 1364 | * MeetmeJoin - 1365 | * MessageWaiting - 1366 | * join - 1367 | * leave - 1368 | * AgentCalled - 1369 | * ParkedCall - Fired after ParkedCalls 1370 | * Cdr - 1371 | * ParkedCallsComplete - 1372 | * QueueParams - 1373 | * QueueMember - 1374 | * QueueStatusEnd - 1375 | * Status - 1376 | * StatusComplete - 1377 | * ZapShowChannels - Fired after ZapShowChannels 1378 | * ZapShowChannelsComplete - 1379 | * 1380 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AMI+Events 1381 | * 1382 | * @param string $event type or * for default handler 1383 | * @param string $callback function 1384 | * @return boolean sucess 1385 | */ 1386 | public function add_event_handler($event, $callback) { 1387 | $event = strtolower($event); 1388 | $this->event_handlers[$event][] = $callback; 1389 | return true; 1390 | } 1391 | 1392 | /** 1393 | * Process event 1394 | * 1395 | * @param array $parameters 1396 | * @return mixed result of event handler or false if no handler was found 1397 | */ 1398 | private function process_event($parameters) { 1399 | $ret = false; 1400 | $handlers = array(); 1401 | $e = strtolower($parameters['Event']); 1402 | $this->log("Got event... $e"); 1403 | 1404 | if(isset($this->event_handlers[$e])) { 1405 | $handlers = array_merge($handlers, $this->event_handlers[$e]); 1406 | } 1407 | if(isset($this->event_handlers['*'])) { 1408 | $handlers = array_merge($handlers, $this->event_handlers['*']); 1409 | } 1410 | 1411 | foreach ($handlers as $handler) { 1412 | if(is_callable($handler)){ 1413 | if (is_array($handler)) { 1414 | $this->log('Execute handler ' . get_class($handler[0]) . '::' . $handler[1]); 1415 | $ret = $handler[0]->$handler[1]($e, $parameters, $this->server, $this->port); 1416 | } else { 1417 | if(is_object($handler)) { 1418 | $this->log("Execute handler " . get_class($handler)); 1419 | } else { 1420 | $this->log("Execute handler $handler"); 1421 | } 1422 | $ret = $handler($e, $parameters, $this->server, $this->port); 1423 | } 1424 | } 1425 | } 1426 | return $ret; 1427 | } 1428 | 1429 | /** 1430 | * Show all entries in the asterisk database 1431 | * 1432 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+Internal+Database 1433 | * 1434 | * @return Array associative array of key=>value 1435 | */ 1436 | public function database_show($family='') { 1437 | if ($this->useCaching && $this->memAstDB != null) { 1438 | if ($family == '') { 1439 | return $this->memAstDB; 1440 | } else { 1441 | $key = '/'.$family; 1442 | if (isset($this->memAstDB[$key])) { 1443 | return array($key => $this->memAstDB[$key]); 1444 | } elseif(isset($this->memAstDBArray[$key])) { 1445 | return $this->memAstDBArray[$key]; 1446 | } else { 1447 | //TODO: this is intensive cache results 1448 | $k = $key; 1449 | $key .= '/'; 1450 | $len = strlen($key); 1451 | $fam_arr = array(); 1452 | foreach ($this->memAstDB as $this_key => $value) { 1453 | if (substr($this_key,0,$len) == $key) { 1454 | $fam_arr[$this_key] = $value; 1455 | } 1456 | } 1457 | $this->memAstDBArray[$k] = $fam_arr; 1458 | return $fam_arr; 1459 | } 1460 | } 1461 | } 1462 | $r = $this->command("database show $family"); 1463 | 1464 | $data = explode("\n",$r["data"]); 1465 | $db = array(); 1466 | 1467 | // Remove the Privilege => Command initial entry that comes from the heading 1468 | // 1469 | array_shift($data); 1470 | foreach ($data as $line) { 1471 | // Note the space here is specifically for PJSIP registration entries, 1472 | // which have a : in them: 1473 | // /registrar/contact/301;@sip:301@192.168.15.125:5062: {"outbound_proxy":"",.... 1474 | $temp = explode(": ",$line,2); 1475 | if (trim($temp[0]) != '' && count($temp) == 2) { 1476 | $temp[1] = isset($temp[1])?$temp[1]:null; 1477 | $db[ trim($temp[0]) ] = trim($temp[1]); 1478 | } 1479 | } 1480 | return $db; 1481 | } 1482 | 1483 | /** 1484 | * Add an entry to the asterisk database 1485 | * 1486 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+Internal+Database 1487 | * 1488 | * @param string $family The family name to use 1489 | * @param string $key The key name to use 1490 | * @param mixed $value The value to add 1491 | * @return bool True if successful 1492 | */ 1493 | public function database_put($family, $key, $value) { 1494 | $write_through = false; 1495 | if (!empty($this->memAstDB)){ 1496 | $keyUsed="/".str_replace(" ","/",$family)."/".str_replace(" ","/",$key); 1497 | if (!isset($this->memAstDB[$keyUsed]) || $this->memAstDB[$keyUsed] != $value) { 1498 | $this->memAstDB[$keyUsed] = $value; 1499 | $write_through = true; 1500 | } 1501 | if(isset($this->memAstDBArray[$keyUsed])) { 1502 | unset($this->memAstDBArray[$keyUsed]); 1503 | } 1504 | } else { 1505 | $write_through = true; 1506 | } 1507 | if ($write_through) { 1508 | $value = str_replace('"','\\"',$value); 1509 | $r = $this->command("database put ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key)." \"".$value."\""); 1510 | return (bool)strstr($r["data"], "success"); 1511 | } 1512 | return true; 1513 | } 1514 | 1515 | /** 1516 | * Get an entry from the asterisk database 1517 | * 1518 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+Internal+Database 1519 | * 1520 | * @param string $family The family name to use 1521 | * @param string $key The key name to use 1522 | * @return mixed Value of the key, or false if error 1523 | */ 1524 | public function database_get($family, $key) { 1525 | if ($this->useCaching) { 1526 | if ($this->memAstDB == null) { 1527 | $this->LoadAstDB(); 1528 | } 1529 | $keyUsed="/".str_replace(" ","/",$family)."/".str_replace(" ","/",$key); 1530 | if (isset($this->memAstDB[$keyUsed])){ 1531 | return $this->memAstDB[$keyUsed]; 1532 | } 1533 | } else { 1534 | $r = $this->command("database get ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key)); 1535 | $data = strpos($r["data"],"Value:"); 1536 | if ($data !== false) { 1537 | return trim(substr($r["data"],6+$data)); 1538 | } 1539 | } 1540 | return false; 1541 | } 1542 | 1543 | 1544 | /** 1545 | * Delete an entry from the asterisk database 1546 | * 1547 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+Internal+Database 1548 | * 1549 | * @param string $family The family name to use 1550 | * @param string $key The key name to use 1551 | * @return bool True if successful 1552 | */ 1553 | public function database_del($family, $key) { 1554 | $r = $this->command("database del ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key)); 1555 | $status = (bool)strstr($r["data"], "removed"); 1556 | if ($status && !empty($this->memAstDB)){ 1557 | $keyUsed="/".str_replace(" ","/",$family)."/".str_replace(" ","/",$key); 1558 | unset($this->memAstDB[$keyUsed]); 1559 | if(isset($this->memAstDBArray[$keyUsed])) { 1560 | unset($this->memAstDBArray[$keyUsed]); 1561 | } 1562 | } 1563 | return $status; 1564 | } 1565 | 1566 | /** 1567 | * Delete a family from the asterisk database 1568 | * 1569 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+Internal+Database 1570 | * 1571 | * @param string $family The family name to use 1572 | * @return bool True if successful 1573 | */ 1574 | public function database_deltree($family) { 1575 | $r = $this->command("database deltree ".str_replace(" ","/",$family)); 1576 | $status = (bool)strstr($r["data"], "removed"); 1577 | if ($status && !empty($this->memAstDB)){ 1578 | $keyUsed="/".str_replace(" ","/",$family); 1579 | foreach($this->memAstDB as $key => $val) { 1580 | $reg = preg_quote($keyUsed,"/"); 1581 | if(preg_match("/^".$reg.".*/",$key)) { 1582 | unset($this->memAstDB[$key]); 1583 | if(isset($this->memAstDBArray[$key])) { 1584 | unset($this->memAstDBArray[$key]); 1585 | } 1586 | } 1587 | } 1588 | } 1589 | return $status; 1590 | } 1591 | 1592 | /** 1593 | * Returns whether a give function exists in this Asterisk install 1594 | * 1595 | * @link https://wiki.asterisk.org/wiki/display/AST/CLI+Syntax+and+Help+Commands#CLISyntaxandHelpCommands-Helpforfunctions,applicationsandmore 1596 | * 1597 | * @param string $func The case sensitve name of the function 1598 | * @return bool True if if it exists 1599 | */ 1600 | public function func_exists($func) { 1601 | $r = $this->command("core show function $func"); 1602 | return (strpos($r['data'],"No function by that name registered") === false); 1603 | } 1604 | 1605 | /** 1606 | * Returns whether a give application exists in this Asterisk install 1607 | * 1608 | * @link https://wiki.asterisk.org/wiki/display/AST/CLI+Syntax+and+Help+Commands#CLISyntaxandHelpCommands-Helpforfunctions,applicationsandmore 1609 | * 1610 | * @param string $app The case in-sensitve name of the application 1611 | * @return bool True if if it exists 1612 | */ 1613 | public function app_exists($app) { 1614 | $r = $this->command("core show application $app"); 1615 | return (strpos($r['data'],"Your application(s) is (are) not registered") === false); 1616 | } 1617 | 1618 | /** 1619 | * Returns whether a give channeltype exists in this Asterisk install 1620 | * 1621 | * @link https://wiki.asterisk.org/wiki/display/AST/CLI+Syntax+and+Help+Commands#CLISyntaxandHelpCommands-Helpforfunctions,applicationsandmore 1622 | * 1623 | * @param string $channel The case in-sensitve name of the channel 1624 | * @return bool True if if it exists 1625 | */ 1626 | public function chan_exists($channel) { 1627 | $r = $this->command("core show channeltype $channel"); 1628 | return (strpos($r['data'],"is not a registered channel driver") === false); 1629 | } 1630 | 1631 | /** 1632 | * Returns whether a give asterisk module is loaded in this Asterisk install 1633 | * 1634 | * @link https://wiki.asterisk.org/wiki/display/AST/CLI+Syntax+and+Help+Commands#CLISyntaxandHelpCommands-Helpforfunctions,applicationsandmore 1635 | * 1636 | * @param string $app The case in-sensitve name of the application 1637 | * @return bool True if if it exists 1638 | */ 1639 | public function mod_loaded($mod) { 1640 | $r = $this->command("module show like $mod"); 1641 | return (preg_match('/1 modules loaded/', $r['data']) > 0); 1642 | } 1643 | 1644 | /** 1645 | * Sets a global var or function to the provided value 1646 | * 1647 | * @param string $var The variable or function to set 1648 | * @param string $val the value to set it to 1649 | * @return array returns the array value from the send_request 1650 | */ 1651 | public function set_global($var, $val) { 1652 | static $pre = ''; 1653 | 1654 | if (! $pre) { 1655 | //TODO: Query Asterisk for it's version during start up 1656 | $pre = version_compare($this->settings['AsteriskVersion'], "1.6.1", "ge") ? 'dialplan' : 'core'; 1657 | } 1658 | return $this->command($pre . ' set global ' . $var . ' ' . $val); 1659 | } 1660 | 1661 | /** 1662 | * Reload module(s) 1663 | * 1664 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_Reload 1665 | * @param string $module 1666 | * @param string $actionid 1667 | */ 1668 | public function Reload($module=NULL, $actionid=NULL) { 1669 | $parameters = array(); 1670 | 1671 | if ($actionid) { 1672 | $parameters['ActionID'] = $actionid; 1673 | } 1674 | if ($module) { 1675 | $parameters['Module'] = $module; 1676 | } 1677 | return $this->send_request('Reload', $parameters); 1678 | } 1679 | 1680 | /** 1681 | * Starts mixmonitor 1682 | * 1683 | * @param string $channel The channel to start recording 1684 | * @param string $file The file to record to 1685 | * @param string $options Options to pass to mixmonitor 1686 | * @param string $postcommand Command to execute after recording 1687 | * @param string $actionid message matching variable 1688 | * 1689 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_MixMonitor 1690 | * 1691 | * @return array returns the array value from the send_request 1692 | */ 1693 | public function mixmonitor($channel, $file, $options='', $postcommand='', $actionid=NULL) { 1694 | if (!$channel || !$file) { 1695 | return false; 1696 | } 1697 | $args = 'mixmonitor start ' . trim($channel) . ' ' . trim($file); 1698 | if ($options || $postcommand) { 1699 | $args .= ',' . trim($options); 1700 | } 1701 | if ($postcommand) { 1702 | $args .= ',' . trim($postcommand); 1703 | } 1704 | return $this->command($args, $actionid); 1705 | } 1706 | 1707 | /** 1708 | * Stops mixmonitor 1709 | * 1710 | * @param string $channel The channel to stop recording 1711 | * @param string $actionid message matching variable 1712 | * 1713 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_MixMonitor 1714 | * 1715 | * @return array returns the array value from the send_request 1716 | */ 1717 | public function stopmixmonitor($channel, $actionid=NULL) { 1718 | if (!$channel) { 1719 | return false; 1720 | } 1721 | $args = 'mixmonitor stop ' . trim($channel); 1722 | return $this->command($args, $actionid); 1723 | } 1724 | 1725 | /** 1726 | * PJSIPShowEndpoint 1727 | * 1728 | * @param string $channel 1729 | * @version 12 1730 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+ManagerAction_PJSIPShowEndpoints 1731 | * 1732 | * @return array returns a key => val array 1733 | */ 1734 | public function PJSIPShowEndpoint($dev) { 1735 | $this->add_event_handler("endpointdetail", array($this, 'Endpoint_catch')); 1736 | $this->add_event_handler("authdetail", array($this, 'Endpoint_catch')); 1737 | $this->add_event_handler("endpointdetailcomplete", array($this, 'Endpoint_catch')); 1738 | $params = array("Endpoint" => $dev); 1739 | $response = $this->send_request('PJSIPShowEndpoint', $params); 1740 | if ($response["Response"] == "Success") { 1741 | $this->wait_response(true); 1742 | stream_set_timeout($this->socket, 30); 1743 | } else { 1744 | return false; 1745 | } 1746 | $res = $this->response_catch; 1747 | // Asterisk 12 can sometimes dump extra garbage after the 1748 | // output of this. So grab it, and discard it, if it's 1749 | // pending. 1750 | // Note that this has been reported as a bug and should 1751 | // be removed, or, wait_response needs to be re-written 1752 | // to keep waiting until it receives the ending event 1753 | // https://issues.asterisk.org/jira/browse/ASTERISK-24331 1754 | usleep(1000); 1755 | stream_set_blocking($this->socket, false); 1756 | while (fgets($this->socket)) { /* do nothing */ } 1757 | stream_set_blocking($this->socket, true); 1758 | unset($this->event_handlers['endpointdetail']); 1759 | unset($this->event_handlers['authdetail']); 1760 | unset($this->event_handlers['endpointdetailcomplete']); 1761 | return $res; 1762 | } 1763 | 1764 | /** 1765 | * Catcher for the pjsip events 1766 | * 1767 | */ 1768 | private function Endpoint_catch($event, $data, $server, $port) { 1769 | switch($event) { 1770 | case 'endpointdetailcomplete': 1771 | stream_set_timeout($this->socket, 0, 1); 1772 | break; 1773 | default: 1774 | $this->response_catch[] = $data; 1775 | } 1776 | } 1777 | 1778 | /** 1779 | * List all channels 1780 | * 1781 | * List currently defined channels and some information about them. 1782 | * 1783 | * @link https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+ManagerAction_CoreShowChannels 1784 | * 1785 | * @return array of all channels currently active 1786 | */ 1787 | public function CoreShowChannels() { 1788 | $this->add_event_handler("coreshowchannel", array($this, 'coreshowchan_catch')); 1789 | $this->add_event_handler("coreshowchannelscomplete", array($this, 'coreshowchan_catch')); 1790 | $response = $this->send_request('CoreShowChannels'); 1791 | if ($response["Response"] == "Success") { 1792 | $this->response_catch = array(); 1793 | $this->wait_response(true); 1794 | stream_set_timeout($this->socket, 30); 1795 | } else { 1796 | return false; 1797 | } 1798 | unset($this->event_handlers['coreshowchannel']); 1799 | unset($this->event_handlers['coreshowchannelscomplete']); 1800 | return $this->response_catch; 1801 | } 1802 | 1803 | /** 1804 | * Core Show Channels Catch 1805 | */ 1806 | private function coreshowchan_catch($event, $data, $server, $port) { 1807 | switch($event) { 1808 | case 'coreshowchannelscomplete': 1809 | stream_set_timeout($this->socket, 0, 1); 1810 | break; 1811 | default: 1812 | $this->response_catch[] = $data; 1813 | } 1814 | } 1815 | } 1816 | 1817 | 1818 | function phpasmanager_error_handler($errno, $errstr, $errfile, $errline) { 1819 | switch ($errno) { 1820 | case E_WARNING: 1821 | case E_USER_WARNING: 1822 | case E_NOTICE: 1823 | case E_USER_NOTICE: 1824 | default: 1825 | //dbug("Got a php-asmanager error of [$errno] $errstr"); 1826 | break; 1827 | } 1828 | /* Don't execute PHP internal error handler */ 1829 | return true; 1830 | } 1831 | -------------------------------------------------------------------------------- /speech.php: -------------------------------------------------------------------------------- 1 | , David Eder 7 | * Copyright (c) 2005 - 2015 Schmooze Com, Inc 8 | * All Rights Reserved. 9 | * 10 | * This software is released under the terms of the GNU Public License v2 11 | * A copy of which is available from http://www.fsf.org/licenses/gpl.txt 12 | * 13 | * @package phpAGI 14 | */ 15 | namespace Asterisk\AMI; 16 | if(!class_exists('AGI')) { 17 | require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi.php'); 18 | } 19 | 20 | /** 21 | * AGI class 22 | * 23 | * @package phpAGI 24 | * @link http://www.voip-info.org/wiki-Asterisk+agi 25 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 26 | * @example examples/input.php Get text input from the user and say it back 27 | * @example examples/ping.php Ping an IP address 28 | */ 29 | class speech extends AGI { 30 | 31 | /** 32 | * Menu. 33 | * 34 | * This function presents the user with a menu and reads the response 35 | * 36 | * @param array $choices has the following structure: 37 | * array('1'=>'*Press 1 for this', // festival reads if prompt starts with * 38 | * '2'=>'some-gsm-without-extension', 39 | * '*'=>'*Press star for help'); 40 | * @return mixed key pressed on sucess, -1 on failure 41 | */ 42 | function menu($choices, $timeout=2000) { 43 | $keys = join('', array_keys($choices)); 44 | $choice = NULL; 45 | while(is_null($choice)) { 46 | foreach($choices as $prompt) { 47 | if($prompt{0} == '*') { 48 | $ret = $this->text2wav(substr($prompt, 1), $keys); 49 | } else { 50 | $ret = $this->stream_file($prompt, $keys); 51 | } 52 | 53 | if($ret['code'] != AGIRES_OK || $ret['result'] == -1) { 54 | $choice = -1; 55 | break; 56 | } 57 | 58 | if($ret['result'] != 0) { 59 | $choice = chr($ret['result']); 60 | break; 61 | } 62 | } 63 | 64 | if(is_null($choice)) { 65 | $ret = $this->get_data('beep', $timeout, 1); 66 | if($ret['code'] != AGIRES_OK || $ret['result'] == -1) { 67 | $choice = -1; 68 | } elseif($ret['result'] != '' && strpos(' '.$keys, $ret['result'])) { 69 | $choice = $ret['result']; 70 | } 71 | } 72 | } 73 | return $choice; 74 | } 75 | 76 | /** 77 | * Use festival to read text. 78 | * 79 | * @example examples/dtmf.php Get DTMF tones from the user and say the digits 80 | * @example examples/input.php Get text input from the user and say it back 81 | * @example examples/ping.php Ping an IP address 82 | * 83 | * @link http://www.cstr.ed.ac.uk/projects/festival/ 84 | * @param string $text 85 | * @param string $escape_digits 86 | * @param integer $frequency 87 | * @return array, see evaluate for return information. 88 | */ 89 | function text2wav($text, $escape_digits='', $frequency=8000) { 90 | // festival TTS config 91 | if(!isset($this->config['festival']['text2wave'])) $this->config['festival']['text2wave'] = $this->which('text2wave'); 92 | 93 | $text = trim($text); 94 | if($text == '') { 95 | return true; 96 | } 97 | 98 | $hash = md5($text); 99 | $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; 100 | $fname .= 'text2wav_' . $hash; 101 | 102 | // create wave file 103 | if(!file_exists("$fname.wav")) { 104 | // write text file 105 | if(!file_exists("$fname.txt")) { 106 | $fp = fopen("$fname.txt", 'w'); 107 | fputs($fp, $text); 108 | fclose($fp); 109 | } 110 | 111 | shell_exec("{$this->config['festival']['text2wave']} -F $frequency -o $fname.wav $fname.txt"); 112 | } else { 113 | touch("$fname.txt"); 114 | touch("$fname.wav"); 115 | } 116 | 117 | // stream it 118 | $ret = $this->stream_file($fname, $escape_digits); 119 | 120 | // clean up old files 121 | $delete = time() - 2592000; // 1 month 122 | foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'text2wav_*') as $file) 123 | if(filemtime($file) < $delete) { 124 | unlink($file); 125 | } 126 | 127 | return $ret; 128 | } 129 | 130 | /** 131 | * Use Cepstral Swift to read text. 132 | * 133 | * @link http://www.cepstral.com/ 134 | * @param string $text 135 | * @param string $escape_digits 136 | * @param integer $frequency 137 | * @return array, see evaluate for return information. 138 | */ 139 | function swift($text, $escape_digits='', $frequency=8000, $voice=NULL) { 140 | // swift TTS config 141 | if(!isset($this->config['cepstral']['swift'])) $this->config['cepstral']['swift'] = $this->which('swift'); 142 | 143 | if(!is_null($voice)) { 144 | $voice = "-n $voice"; 145 | } elseif(isset($this->config['cepstral']['voice'])) { 146 | $voice = "-n {$this->config['cepstral']['voice']}"; 147 | } 148 | 149 | $text = trim($text); 150 | if($text == '') { 151 | return true; 152 | } 153 | 154 | $hash = md5($text); 155 | $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR; 156 | $fname .= 'swift_' . $hash; 157 | 158 | // create wave file 159 | if(!file_exists("$fname.wav")) { 160 | // write text file 161 | if(!file_exists("$fname.txt")) { 162 | $fp = fopen("$fname.txt", 'w'); 163 | fputs($fp, $text); 164 | fclose($fp); 165 | } 166 | 167 | shell_exec("{$this->config['cepstral']['swift']} -p audio/channels=1,audio/sampling-rate=$frequency $voice -o $fname.wav -f $fname.txt"); 168 | } 169 | 170 | // stream it 171 | $ret = $this->stream_file($fname, $escape_digits); 172 | 173 | // clean up old files 174 | $delete = time() - 2592000; // 1 month 175 | foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'swift_*') as $file) 176 | if(filemtime($file) < $delete) { 177 | unlink($file); 178 | } 179 | 180 | return $ret; 181 | } 182 | 183 | /** 184 | * Text Input. 185 | * 186 | * Based on ideas found at http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText 187 | * 188 | * Example: 189 | * UC H LC i , SP h o w SP a r e SP y o u ? 190 | * $string = '*8'.'44*'.'*5'.'444*'.'00*'.'0*'.'44*'.'666*'.'9*'.'0*'.'2*'.'777*'.'33*'.'0*'.'999*'.'666*'.'88*'.'0000*'; 191 | * 192 | * @link http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText 193 | * @example examples/input.php Get text input from the user and say it back 194 | * 195 | * @return string 196 | */ 197 | function text_input($mode='NUMERIC') { 198 | $alpha = array( 199 | 'k0'=>' ', 'k00'=>',', 'k000'=>'.', 'k0000'=>'?', 'k00000'=>'0', 200 | 'k1'=>'!', 'k11'=>':', 'k111'=>';', 'k1111'=>'#', 'k11111'=>'1', 201 | 'k2'=>'A', 'k22'=>'B', 'k222'=>'C', 'k2222'=>'2', 202 | 'k3'=>'D', 'k33'=>'E', 'k333'=>'F', 'k3333'=>'3', 203 | 'k4'=>'G', 'k44'=>'H', 'k444'=>'I', 'k4444'=>'4', 204 | 'k5'=>'J', 'k55'=>'K', 'k555'=>'L', 'k5555'=>'5', 205 | 'k6'=>'M', 'k66'=>'N', 'k666'=>'O', 'k6666'=>'6', 206 | 'k7'=>'P', 'k77'=>'Q', 'k777'=>'R', 'k7777'=>'S', 'k77777'=>'7', 207 | 'k8'=>'T', 'k88'=>'U', 'k888'=>'V', 'k8888'=>'8', 208 | 'k9'=>'W', 'k99'=>'X', 'k999'=>'Y', 'k9999'=>'Z', 'k99999'=>'9' 209 | ); 210 | $symbol = array( 211 | 'k0'=>'=', 212 | 'k1'=>'<', 'k11'=>'(', 'k111'=>'[', 'k1111'=>'{', 'k11111'=>'1', 213 | 'k2'=>'@', 'k22'=>'$', 'k222'=>'&', 'k2222'=>'%', 'k22222'=>'2', 214 | 'k3'=>'>', 'k33'=>')', 'k333'=>']', 'k3333'=>'}', 'k33333'=>'3', 215 | 'k4'=>'+', 'k44'=>'-', 'k444'=>'*', 'k4444'=>'/', 'k44444'=>'4', 216 | 'k5'=>"'", 'k55'=>'`', 'k555'=>'5', 217 | 'k6'=>'"', 'k66'=>'6', 218 | 'k7'=>'^', 'k77'=>'7', 219 | 'k8'=>"\\",'k88'=>'|', 'k888'=>'8', 220 | 'k9'=>'_', 'k99'=>'~', 'k999'=>'9' 221 | ); 222 | $text = ''; 223 | do { 224 | $command = false; 225 | $result = $this->get_data('beep'); 226 | foreach(explode('*', $result['result']) as $code) { 227 | if($command) { 228 | switch($code{0}) { 229 | case '2': $text = substr($text, 0, strlen($text) - 1); break; // backspace 230 | case '5': $mode = 'LOWERCASE'; break; 231 | case '6': $mode = 'NUMERIC'; break; 232 | case '7': $mode = 'SYMBOL'; break; 233 | case '8': $mode = 'UPPERCASE'; break; 234 | case '9': $text = explode(' ', $text); unset($text[count($text)-1]); $text = join(' ', $text); break; // backspace a word 235 | } 236 | $code = substr($code, 1); 237 | $command = false; 238 | } 239 | if($code == '') 240 | $command = true; 241 | elseif($mode == 'NUMERIC') 242 | $text .= $code; 243 | elseif($mode == 'UPPERCASE' && isset($alpha['k'.$code])) 244 | $text .= $alpha['k'.$code]; 245 | elseif($mode == 'LOWERCASE' && isset($alpha['k'.$code])) 246 | $text .= strtolower($alpha['k'.$code]); 247 | elseif($mode == 'SYMBOL' && isset($symbol['k'.$code])) 248 | $text .= $symbol['k'.$code]; 249 | } 250 | $this->say_punctuation($text); 251 | } while(substr($result['result'], -2) == '**'); 252 | return $text; 253 | } 254 | 255 | /** 256 | * Say Puncutation in a string. 257 | * 258 | * @param string $text 259 | * @param string $escape_digits 260 | * @param integer $frequency 261 | * @return array, see evaluate for return information. 262 | */ 263 | function say_punctuation($text, $escape_digits='', $frequency=8000) { 264 | for($i = 0; $i < strlen($text); $i++) { 265 | switch($text{$i}) { 266 | case ' ': $ret .= 'SPACE '; 267 | case ',': $ret .= 'COMMA '; break; 268 | case '.': $ret .= 'PERIOD '; break; 269 | case '?': $ret .= 'QUESTION MARK '; break; 270 | case '!': $ret .= 'EXPLANATION POINT '; break; 271 | case ':': $ret .= 'COLON '; break; 272 | case ';': $ret .= 'SEMICOLON '; break; 273 | case '#': $ret .= 'POUND '; break; 274 | case '=': $ret .= 'EQUALS '; break; 275 | case '<': $ret .= 'LESS THAN '; break; 276 | case '(': $ret .= 'LEFT PARENTHESIS '; break; 277 | case '[': $ret .= 'LEFT BRACKET '; break; 278 | case '{': $ret .= 'LEFT BRACE '; break; 279 | case '@': $ret .= 'AT '; break; 280 | case '$': $ret .= 'DOLLAR SIGN '; break; 281 | case '&': $ret .= 'AMPERSAND '; break; 282 | case '%': $ret .= 'PERCENT '; break; 283 | case '>': $ret .= 'GREATER THAN '; break; 284 | case ')': $ret .= 'RIGHT PARENTHESIS '; break; 285 | case ']': $ret .= 'RIGHT BRACKET '; break; 286 | case '}': $ret .= 'RIGHT BRACE '; break; 287 | case '+': $ret .= 'PLUS '; break; 288 | case '-': $ret .= 'MINUS '; break; 289 | case '*': $ret .= 'ASTERISK '; break; 290 | case '/': $ret .= 'SLASH '; break; 291 | case "'": $ret .= 'SINGLE QUOTE '; break; 292 | case '`': $ret .= 'BACK TICK '; break; 293 | case '"': $ret .= 'QUOTE '; break; 294 | case '^': $ret .= 'CAROT '; break; 295 | case "\\": $ret .= 'BACK SLASH '; break; 296 | case '|': $ret .= 'BAR '; break; 297 | case '_': $ret .= 'UNDERSCORE '; break; 298 | case '~': $ret .= 'TILDE '; break; 299 | default: $ret .= $text{$i} . ' '; break; 300 | } 301 | } 302 | return $this->text2wav($ret, $escape_digits, $frequency); 303 | } 304 | 305 | /** 306 | * Find an execuable in the path. 307 | * 308 | * @param string $cmd command to find 309 | * @param string $checkpath path to check 310 | * @return string the path to the command 311 | */ 312 | private function which($cmd, $checkpath=NULL) { 313 | global $_ENV; 314 | $chpath = is_null($checkpath) ? $_ENV['PATH'] : $checkpath; 315 | 316 | foreach(explode(PATH_SEPERATOR, $chpath) as $path) 317 | if(!function_exists('is_executable') || is_executable($path . DIRECTORY_SEPERATOR . $cmd)) 318 | return $path . DIRECTORY_SEPERATOR . $cmd; 319 | 320 | if(is_null($checkpath)) 321 | { 322 | if(substr(strtoupper(PHP_OS, 0, 3)) != 'WIN') 323 | return $this->which($cmd, '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:'. 324 | '/usr/X11R6/bin:/usr/local/apache/bin:/usr/local/mysql/bin'); 325 | } 326 | return false; 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /utests/ManagerTest.php: -------------------------------------------------------------------------------- 1 | $user['username'], 12 | "secret" => $user['secret'] 13 | ); 14 | $manager = new Asterisk\AMI\Manager($config); 15 | $manager->connect(); 16 | 17 | self::$manager = $manager; 18 | } 19 | 20 | public function testCoreSettings() { 21 | $settings = self::$manager->CoreSettings(); 22 | 23 | $this->assertNotEmpty($settings); 24 | $this->assertArrayHasKey("AMIversion",$settings); 25 | $this->assertArrayHasKey("AsteriskVersion",$settings); 26 | } 27 | 28 | /* 29 | public function testOriginate() { 30 | $user = $this->getValidUser(); 31 | $this->assertNotEmpty($user); 32 | print_r($user); 33 | $request = self::$manager->Originate("SIP/1002@from-internal","*43","default",1,1,"Asterisk Automatic Wardial","","","",""); 34 | print_r($request); 35 | } 36 | */ 37 | 38 | public function testSendRequest() { 39 | $request = self::$manager->send_request("ListCommands"); 40 | $this->assertEquals("Success", $request['Response']); 41 | } 42 | 43 | public function testAbsoluteTimeout() { 44 | $request = self::$manager->AbsoluteTimeout("SIP/1000","30"); 45 | $this->assertEquals("No such channel", $request['Message']); 46 | } 47 | 48 | /* 49 | public function testAgentLogoff() { 50 | $request = self::$manager->AgentLogoff("1","false"); 51 | print_r($request); 52 | $request = self::$manager->AgentLogoff("1","true"); 53 | } 54 | 55 | public function testAgents() { 56 | $agents = self::$manager->Agents(); 57 | print_r($agents); 58 | } 59 | */ 60 | 61 | public function testCommand() { 62 | $help = self::$manager->Command("core show help"); 63 | 64 | $this->assertNotEmpty($help); 65 | $this->assertArrayHasKey("data",$help); 66 | $this->assertEquals("Follows", $help['Response']); 67 | } 68 | 69 | public function testCodecs() { 70 | $codecs = self::$manager->Codecs(); 71 | $this->assertNotEmpty($codecs); 72 | } 73 | 74 | private function getValidUser() { 75 | $peers = self::$manager->Command("sip show users"); 76 | $lines = explode("\n",$peers['data']); 77 | foreach($lines as $line) { 78 | if(preg_match("/^(\d*)\s/",$line,$matches)) { 79 | return "SIP/".$matches[1]; 80 | } 81 | } 82 | return null; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /utests/config.php: -------------------------------------------------------------------------------- 1 |