marcelog/PAGI

View on GitHub
src/PAGI/Client/AbstractClient.php

Summary

Maintainability
D
2 days
Test Coverage
<?php
/**
 * An abstract AGI client.
 *
 * PHP Version 5
 *
 * @category Pagi
 * @package  Client
 * @author   Marcelo Gornstein <marcelog@gmail.com>
 * @license  http://marcelog.github.com/PAGI/ Apache License 2.0
 * @link     http://marcelog.github.com/PAGI/
 *
 * Copyright 2011 Marcelo Gornstein <marcelog@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
namespace PAGI\Client;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use PAGI\Logger\Asterisk\Impl\AsteriskLoggerImpl;
use PAGI\Client\Result\Result;
use PAGI\Client\Result\FaxResult;
use PAGI\Client\Result\ExecResult;
use PAGI\Client\Result\DialResult;
use PAGI\Client\Result\DigitReadResult;
use PAGI\Client\Result\DataReadResult;
use PAGI\Client\Result\PlayResult;
use PAGI\Client\Result\RecordResult;
use PAGI\Client\Result\AmdResult;
use PAGI\Client\ChannelStatus;
use PAGI\Exception\ExecuteCommandException;
use PAGI\Exception\DatabaseInvalidEntryException;
use PAGI\Exception\PAGIException;
use PAGI\Exception\ChannelDownException;
use PAGI\Exception\SoundFileException;
use PAGI\Exception\InvalidCommandException;
use PAGI\Client\IClient;
use PAGI\ChannelVariables\Impl\ChannelVariablesFacade;
use PAGI\CDR\Impl\CDRFacade;
use PAGI\CallerId\Impl\CallerIdFacade;

/**
 * An abstract AGI client.
 *
 * PHP Version 5
 *
 * @category Pagi
 * @package  Client
 * @author   Marcelo Gornstein <marcelog@gmail.com>
 * @license  http://marcelog.github.com/PAGI/ Apache License 2.0
 * @link     http://marcelog.github.com/PAGI/
 */
abstract class AbstractClient implements IClient
{
    /**
     * PSR-3 logger.
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Initial channel variables given by asterisk at start.
     * @var string[]
     */
    protected $variables = array();

    /**
     * Initial arguments given by the user in the dialplan.
     * @var string[]
     */
    protected $arguments = array();

    protected $cdrInstance = false;
    protected $channelVariablesInstance = false;
    protected $clidInstance = false;

    /**
     * Sends a command to asterisk. Returns an array with:
     * [0] => AGI Result (3 digits)
     * [1] => Command result
     * [2] => Result data.
     *
     * @param string $text Command
     *
     * @throws ChannelDownException
     * @throws InvalidCommandException
     *
     * @return Result
     */
    abstract protected function send($text);

    /**
     * Opens connection to agi. Will also read initial channel variables given
     * by asterisk when launching the agi.
     *
     * @return void
     */
    abstract protected function open();

    /**
     * Closes the connection to agi.
     *
     * @return void
     */
    abstract protected function close();

    /**
     * Returns a result object given a string (the agi result after executing
     * a command).
     *
     * @param unknown_type $text
     * @throws ChannelDownException
     * @throws InvalidCommandException
     *
     * @return Result
     */
    protected function getResultFromResultString($text)
    {
        if ($text == 'HANGUP') {
            throw new ChannelDownException(new Result('511 asterisk hangup'));
        }
        $result = new Result($text);
        switch ($result->getCode()) {
            case 200:
                return $result;
            case 511:
                throw new ChannelDownException($result);
            case 510:
            case 520:
            default:
                break;
        }
        throw new InvalidCommandException($result);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::amd()
     */
    public function amd($options = array())
    {
        $knownOptions = array(
            'initialSilence', 'greeting', 'afterGreetingSilence', 'totalAnalysisTime',
            'miniumWordLength', 'betweenWordSilence', 'maximumNumberOfWords',
            'silenceThreshold', 'maximumWordLength'
        );
        $args = array();
        $total = count($knownOptions);
        for ($i = 0; $i < $total; $i++) {
            $key = $knownOptions[$i];
            if (isset($options[$key])) {
                $args[] = $options[$key];
            } else {
                $args[] = '';
            }
        }
        $result = new AmdResult($this->exec('AMD', $args));
        $result->setStatus($this->getFullVariable('AMDSTATUS'));
        $result->setCause($this->getFullVariable('AMDCAUSE'));
        return $result;
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::faxSend()
     */
    public function faxSend($tiffFile)
    {
        $result = new FaxResult($this->exec('SendFax', array($tiffFile, 'a')));
        $result->setResult($this->getFullVariable('FAXSTATUS') === 'SUCCESS');
        $result->setBitrate($this->getFullVariable('FAXBITRATE'));
        $result->setResolution($this->getFullVariable('FAXRESOLUTION'));
        $result->setPages($this->getFullVariable('FAXPAGES'));
        $result->setError($this->getFullVariable('FAXERROR'));
        $result->setRemoteStationId($this->getFullVariable('REMOTESTATIONID'));
        $result->setLocalStationId($this->getFullVariable('LOCALSTATIONID'));
        $result->setLocalHeaderInfo($this->getFullVariable('LOCALHEADERINFO'));
        return $result;
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::faxReceive()
     */
    public function faxReceive($tiffFile)
    {
        $result = new FaxResult($this->exec('ReceiveFax', array($tiffFile)));
        $result->setResult($this->getFullVariable('FAXSTATUS') === 'SUCCESS');
        $result->setBitrate($this->getFullVariable('FAXBITRATE'));
        $result->setResolution($this->getFullVariable('FAXRESOLUTION'));
        $result->setPages($this->getFullVariable('FAXPAGES'));
        $result->setError($this->getFullVariable('FAXERROR'));
        $result->setRemoteStationId($this->getFullVariable('REMOTESTATIONID'));
        $result->setLocalStationId($this->getFullVariable('LOCALSTATIONID'));
        $result->setLocalHeaderInfo($this->getFullVariable('LOCALHEADERINFO'));
        return $result;
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::dial()
     */
    public function dial($channel, array $options = array())
    {
        $start = time();
        array_unshift($options, $channel);
        $result = new DialResult($this->exec('Dial', $options));
        $end = time();
        $result->setPeerName($this->getFullVariable('DIALEDPEERNAME'));
        $result->setPeerNumber($this->getFullVariable('DIALEDPEERNUMBER'));
        $result->setDialedTime($end - $start);
        $result->setAnsweredTime($this->getFullVariable('ANSWEREDTIME'));
        $result->setDialStatus($this->getFullVariable('DIALSTATUS'));
        $result->setDynamicFeatures($this->getFullVariable('DYNAMIC_FEATURES'));
        return $result;
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::exec()
     */
    public function exec($application, array $options = array())
    {
        $cmd = implode(
            ' ',
            array(
                'EXEC', '"' . $application . '"',
                '"' . implode(',', $options) . '"'
            )
        );
        return new ExecResult($this->send($cmd));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::setAutoHangup()
     */
    public function setAutoHangup($time)
    {
        $this->send(implode(' ', array('SET', 'AUTOHANGUP', $time)));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::channelStatus()
     */
    public function channelStatus($channel = '')
    {
        $cmd = implode(' ', array('CHANNEL', 'STATUS'));
        if ($channel !== '') {
            $cmd .= ' "' . $channel . '"';
        }
        $result = $this->send($cmd);
        return intval($result->getResult());
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::streamFile()
     */
    public function streamFile($file, $escapeDigits = '')
    {
        $cmd = implode(
            ' ',
            array(
                'STREAM', 'FILE', '"' . $file . '"', '"' . $escapeDigits . '"'
            )
        );
        return new PlayResult(new DigitReadResult($this->send($cmd)));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::record()
     */
    public function record($file, $format, $escapeDigits, $maxRecordTime = -1, $silence = false)
    {
        $cmd = implode(
            ' ',
            array(
                'RECORD', 'FILE',
                '"' . $file . '"', '"' . $format . '"',
                '"' . $escapeDigits . '"', '"' . $maxRecordTime . '"'
            )
        );
        if ($silence !== false) {
            $cmd .= ' "s=' . $silence . '"';
        }
        return new RecordResult($this->send($cmd));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::setPriority()
     */
    public function setPriority($priority)
    {
        $cmd = implode(' ', array('SET', 'PRIORITY', '"' . $priority . '"'));
        $this->send($cmd);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::setExtension()
     */
    public function setExtension($extension)
    {
        $cmd = implode(' ', array('SET', 'EXTENSION', '"' . $extension . '"'));
        $this->send($cmd);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::setContext()
     */
    public function setContext($context)
    {
        $cmd = implode(' ', array('SET', 'CONTEXT', '"' . $context . '"'));
        $this->send($cmd);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::setCallerId()
     */
    public function setCallerId($name, $number)
    {
        $clid = '\\"' . $name . '\\"<' . $number . '>';
        $cmd = implode(' ', array('SET', 'CALLERID', '"' . $clid . '"'));
        $this->send($cmd);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::setMusic()
     */
    public function setMusic($enable, $class = false)
    {
        $cmd = implode(' ', array('SET', 'MUSIC', $enable ? 'on' : 'off'));
        if ($class !== false) {
            $cmd .= ' "' . $class . '"';
        }
        $this->send($cmd);
    }
    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getData()
     */
    public function getData($file, $maxTime, $maxDigits)
    {
        $timeout = false;
        $cmd = implode(
            ' ',
            array(
                'GET', 'DATA',
                '"' . $file . '"',
                '"' . $maxTime . '"',
                '"' . $maxDigits . '"'
            )
        );
        return new PlayResult(new DataReadResult($this->send($cmd)));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getOption()
     */
    public function getOption($file, $escapeDigits, $maxTime)
    {
        return $this->playAndRead(implode(' ', array(
            'GET', 'OPTION',
            '"' . $file . '"', '"' . $escapeDigits . '"',
            '"' . $maxTime . '"'
        )));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sayTime()
     */
    public function sayTime($time, $escapeDigits = '')
    {
        return $this->playAndRead(implode(' ', array(
            'SAY', 'TIME', '"' . $time . '"','"' . $escapeDigits . '"'
        )));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sayDate()
     */
    public function sayDate($time, $escapeDigits = '')
    {
        return $this->playAndRead(implode(' ', array(
            'SAY', 'DATE', '"' . $time . '"', '"' . $escapeDigits . '"'
        )));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sayDateTime()
     */
    public function sayDateTime($time, $format, $escapeDigits = '')
    {
        return $this->playAndRead(implode(' ', array(
            'SAY', 'DATETIME', '"' . $time . '"', '"' . $escapeDigits . '"', '"' . $format . '"'
        )));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sayDigits()
     */
    public function sayDigits($digits, $escapeDigits = '')
    {
        return $this->playAndRead(implode(' ', array(
            'SAY', 'DIGITS', '"' . $digits . '"', '"' . $escapeDigits . '"'
        )));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sayNumber()
     */
    public function sayNumber($digits, $escapeDigits = '')
    {
        return $this->playAndRead(implode(' ', array(
            'SAY', 'NUMBER', '"' . $digits . '"', '"' . $escapeDigits . '"'
        )));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sayAlpha()
     */
    public function sayAlpha($what, $escapeDigits = '')
    {
        return $this->playAndRead(implode(' ', array(
            'SAY', 'ALPHA', '"' . $what . '"', '"' . $escapeDigits . '"'
        )));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sayPhonetic()
     */
    public function sayPhonetic($what, $escapeDigits = '')
    {
        return $this->playAndRead(implode(' ', array(
            'SAY', 'PHONETIC', '"' . $what . '"', '"' . $escapeDigits . '"'
        )));
    }

    private function playAndRead($cmd)
    {
        return new PlayResult(new DigitReadResult($this->send($cmd)));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::waitDigit()
     */
    public function waitDigit($timeout)
    {
        $cmd = implode(' ', array('WAIT', 'FOR', 'DIGIT', '"' . $timeout . '"'));
        return new DigitReadResult($this->send($cmd));
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::answer()
     */
    public function answer()
    {
        $result = $this->send('ANSWER');
        if ($result->isResult(-1)) {
            throw new ChannelDownException('Answer failed');
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::hangup()
     */
    public function hangup($channel = false)
    {
        $cmd = 'HANGUP';
        if ($channel !== false) {
            $cmd .= ' "' . $channel . '"';
        }
        $result = $this->send($cmd);
        if ($result->isResult(-1)) {
            throw new ChannelDownException('Hangup failed');
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getVariable()
     */
    public function getVariable($name)
    {
        $cmd = implode(' ', array('GET', 'VARIABLE', '"' . $name . '"'));
        $result = $this->send($cmd);
        if ($result->isResult(0)) {
            return false;
        }
        return substr($result->getData(), 1, -1);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getFullVariable()
     */
    public function getFullVariable($name, $channel = false)
    {
        $cmd = implode(
            ' ',
            array('GET', 'FULL', 'VARIABLE', '"${' . $name . '}"')
        );
        if ($channel !== false) {
            $cmd .= ' "' . $channel . '"';
        }
        $result = $this->send($cmd);
        if ($result->isResult(0)) {
            return false;
        }
        return substr($result->getData(), 1, -1);
    }


    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::setVariable()
     */
    public function setVariable($name, $value)
    {
        $this->send(
            implode(
                ' ',
                array(
                    'SET', 'VARIABLE',
                    '"' . $name . '"',
                    '"' . str_replace('"', '\\"', $value) . '"'
                )
            )
        );
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::log()
     */
    public function log($msg, $priority = 'NOTICE')
    {
        $msg = str_replace("\r", '', $msg);
        $msg = explode("\n", $msg);
        foreach ($msg as $line) {
            $this->exec(
                'LOG',
                array($priority, str_replace('"', '\\"', $line))
            );
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::log()
     */
    public function consoleLog($msg, $level = 1)
    {
        $msg = str_replace("\r", '', $msg);
        $msg = explode("\n", $msg);
        foreach ($msg as $line) {
            if (strlen($line) < 1) {
                continue;
            }
            $this->send(
                'VERBOSE "' . str_replace('"', '\\"', $line) . '" ' . $level
            );
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getAsteriskLogger()
     */
    public function getAsteriskLogger()
    {
        return AsteriskLoggerImpl::getLogger($this);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::databaseDel()
     */
    public function databaseDel($family, $key)
    {
        $cmd = implode(
            ' ',
            array(
                'DATABASE', 'DELTREE', '"' . $family. '"', '"' . $key . '"'
            )
        );
        $result = $this->send($cmd);
        if ($result->isResult(0)) {
            throw new DatabaseInvalidEntryException('Invalid family or key.');
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::databaseDeltree()
     */
    public function databaseDeltree($family, $key = false)
    {
        $cmd = implode(' ', array('DATABASE', 'DELTREE', '"' . $family . '"'));
        if ($key !== false) {
            $cmd .= ' "' . $key . '"';
        }
        $result = $this->send($cmd);
        if ($result->isResult(0)) {
            throw new DatabaseInvalidEntryException('Invalid family or key.');
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::databaseGet()
     */
    public function databaseGet($family, $key)
    {
        $cmd = implode(
            ' ',
            array('DATABASE', 'GET', '"' . $family. '"', '"' . $key . '"')
        );
        $result = $this->send($cmd);
        if ($result->isResult(0)) {
            throw new DatabaseInvalidEntryException('Invalid family or key.');
        }
        return substr($result->getData(), 1, -1);
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::databasePut()
     */
    public function databasePut($family, $key, $value)
    {
        $cmd = implode(
            ' ',
            array(
                'DATABASE', 'PUT',
                '"' . $family . '"',
                '"' . $key . '"',
                '"' . $value . '"',
            )
        );
        $result = $this->send($cmd);
        if ($result->isResult(0)) {
            throw new DatabaseInvalidEntryException('Invalid family or key.');
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sendText()
     */
    public function sendText($text)
    {
        $cmd = implode(' ', array('SEND', 'TEXT', '"' . $text . '"'));
        $result = $this->send($cmd);
        if ($result->isResult(-1)) {
            throw new PAGIException('Command failed');
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::sendImage()
     */
    public function sendImage($filename)
    {
        $cmd = implode(' ', array('SEND', 'IMAGE', '"' . $filename . '"'));
        $result = $this->send($cmd);
        if ($result->isResult(-1)) {
            throw new PAGIException('Command failed');
        }
    }
    /**
     * Returns true if the current line marks the end of the environment variables.
     *
     * @param string $line
     *
     * @return boolean
     */
    protected function isEndOfEnvironmentVariables($line)
    {
        return strlen($line) < 1;
    }

    /**
     * Will read and save an environment variable as either a variable or an argument.
     *
     * @param string $line
     *
     * @return void
     */
    protected function readEnvironmentVariable($line)
    {
        list($key, $value) = explode(':', substr($line, 4), 2);
        if (strncmp($key, 'arg_', 4) === 0) {
            $this->arguments[substr($key, 4)] = $value;
        } else {
            $this->variables[$key] = $value;
        }
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getChannelVariables()
     */
    public function getChannelVariables()
    {
        if ($this->channelVariablesInstance === false) {
            $this->channelVariablesInstance = new ChannelVariablesFacade(
                $this->variables,
                $this->arguments
            );
        }
        return $this->channelVariablesInstance;
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getCDR()
     */
    public function getCDR()
    {
        if ($this->cdrInstance === false) {
            $this->cdrInstance = new CDRFacade($this);
        }
        return $this->cdrInstance;
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::getCallerId()
     */
    public function getCallerId()
    {
        if ($this->clidInstance === false) {
            $this->clidInstance = new CallerIdFacade($this);
        }
        return $this->clidInstance;
    }

    /**
     * (non-PHPdoc)
     * @see IClient::indicateProgress()
     */
    public function indicateProgress()
    {
        return $this->exec('Progress', array());
    }

    /**
     * (non-PHPdoc)
     * @see IClient::indicateBusy()
     */
    public function indicateBusy($timeout)
    {
        return $this->exec('Busy', array($timeout));
    }

    /**
     * (non-PHPdoc)
     * @see IClient::indicateCongestion()
     */
    public function indicateCongestion($timeout)
    {
        return $this->exec('Congestion', array($timeout));
    }
    /**
     * (non-PHPdoc)
     * @see IClient::playDialTone()
     */
    public function playDialTone()
    {
        return $this->playTone('dial');
    }

    /**
     * (non-PHPdoc)
     * @see IClient::playBusyTone()
     */
    public function playBusyTone()
    {
        return $this->playTone('Busy');
    }

    /**
     * (non-PHPdoc)
     * @see IClient::playCongestionTone()
     */
    public function playCongestionTone()
    {
        return $this->playTone('Congestion');
    }

    /**
     * (non-PHPdoc)
     * @see IClient::playTone()
     */
    public function playTone($tone)
    {
        return $this->exec('PlayTones', array($tone));
    }
    /**
     * (non-PHPdoc)
     * @see IClient::playCustomTones()
     */
    public function playCustomTones(array $frequencies)
    {
        return $this->exec('PlayTones', $frequencies);
    }
    /**
     * (non-PHPdoc)
     * @see IClient::stopPlayingTones()
     */
    public function stopPlayingTones()
    {
        return $this->exec('StopPlayTones', array());
    }

    /**
     * (non-PHPdoc)
     * @see IClient::createNode()
     */
    public function createNode($name)
    {
        $node = new \PAGI\Node\Node();
        return $node->setName($name)->setAgiClient($this);
    }

    /**
     * (non-PHPdoc)
     * @see IClient::sipHeaderAdd()
     */
    public function sipHeaderAdd($name, $value)
    {
        $this->exec('SipAddHeader', array("$name: $value"));
    }

    /**
     * (non-PHPdoc)
     * @see IClient::sipHeaderRemove()
     */
    public function sipHeaderRemove($name)
    {
        $result = $this->exec('SipRemoveHeader', array($name));
        return $result->getData();
    }

    /**
     * (non-PHPdoc)
     * @see PAGI\Client.IClient::createNodeController()
     */
    public function createNodeController($name)
    {
        $controller = new \PAGI\Node\NodeController();
        return $controller->setName($name)->setAgiClient($this);
    }

    /**
     * Sets the logger implementation.
     *
     * @param LoggerInterface $logger The PSR3-Logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}