| Total Complexity | 108 |
| Total Lines | 758 |
| Duplicated Lines | 0 % |
| Changes | 4 | ||
| Bugs | 0 | Features | 0 |
Complex classes like ExtensionsConf often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ExtensionsConf, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 16 | class ExtensionsConf extends ConfigClass |
||
| 17 | { |
||
| 18 | protected string $description = 'extensions.conf'; |
||
| 19 | |||
| 20 | /** |
||
| 21 | * Основной генератор extensions.conf |
||
| 22 | */ |
||
| 23 | protected function generateConfigProtected(): void |
||
| 56 | } |
||
| 57 | |||
| 58 | /** |
||
| 59 | * Генератор прочих контекстов. |
||
| 60 | * |
||
| 61 | * @param $conf |
||
| 62 | */ |
||
| 63 | private function generateOtherExten(&$conf): void |
||
| 64 | { |
||
| 65 | $extension = 'X!'; |
||
| 66 | // Контекст для AMI originate. Без него отображается не корректный CallerID. |
||
| 67 | $conf .= '[sipregistrations]' . "\n\n"; |
||
| 68 | |||
| 69 | $conf .= '[messages]' . "\n" . |
||
| 70 | 'exten => _' . $extension . ',1,MessageSend(sip:${EXTEN},"${CALLERID(name)}"${MESSAGE(from)})' . "\n\n"; |
||
| 71 | |||
| 72 | $conf .= '[internal-originate]' . " \n"; |
||
| 73 | $conf .= 'exten => _' . $extension . ',1,NoOP(Hint ${HINT} exten ${EXTEN} )' . " \n"; |
||
| 74 | $conf .= '; Если это originate, то скроем один CDR.' . " \n\t"; |
||
| 75 | $conf .= 'same => n,ExecIf($["${pt1c_cid}x" != "x"]?Set(CALLERID(num)=${pt1c_cid}))' . " \n\t"; |
||
| 76 | |||
| 77 | $conf .= 'same => n,ExecIf($["${CUT(CHANNEL,\;,2)}" == "2"]?Set(__PT1C_SIP_HEADER=${SIPADDHEADER}))' . " \n\t"; |
||
| 78 | $conf .= 'same => n,ExecIf($["${peer_mobile}x" != "x"]?Set(ADDITIONAL_PEER=&Local/${peer_mobile}@outgoing/n))' . " \n\t"; |
||
| 79 | |||
| 80 | // Описываем возможность прыжка в пользовательский sub контекст. |
||
| 81 | $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t"; |
||
| 82 | $conf .= 'same => n,Dial(Local/${EXTEN}@internal-users/n${ADDITIONAL_PEER},60,cTteKkHhb(originate_create_chan,s,1))' . " \n\n"; |
||
| 83 | |||
| 84 | $conf .= '[originate_create_chan]' . " \n"; |
||
| 85 | $conf .= 'exten => s,1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t"; |
||
| 86 | $conf .= 'same => n,return' . " \n\n"; |
||
| 87 | |||
| 88 | $conf .= '[dial_create_chan]' . " \n"; |
||
| 89 | $conf .= 'exten => s,1,Gosub(lua_${ISTRANSFER}dial_create_chan,${EXTEN},1)' . "\n\t"; |
||
| 90 | $conf .= 'same => n,Set(pt1c_is_dst=1)' . " \n\t"; |
||
| 91 | $conf .= 'same => n,ExecIf($["${PT1C_SIP_HEADER}x" != "x"]?Set(PJSIP_HEADER(add,${CUT(PT1C_SIP_HEADER,:,1)})=${CUT(PT1C_SIP_HEADER,:,2)}))' . " \n\t"; |
||
| 92 | $conf .= 'same => n,Set(__PT1C_SIP_HEADER=${UNDEFINED})' . " \n\t"; |
||
| 93 | $conf .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . " \n\t"; |
||
| 94 | $conf .= 'same => n,return' . " \n\n"; |
||
| 95 | |||
| 96 | $conf .= '[hangup_handler]' . "\n"; |
||
| 97 | $conf .= 'exten => s,1,NoOp(--- hangup - ${CHANNEL} ---)' . "\n\t"; |
||
| 98 | $conf .= 'same => n,Gosub(hangup_chan,${EXTEN},1)' . "\n\t"; |
||
| 99 | |||
| 100 | $conf .= 'same => n,return' . "\n\n"; |
||
| 101 | |||
| 102 | $conf .= '[set_orign_chan]' . "\n"; |
||
| 103 | $conf .= 'exten => s,1,Wait(0.2)' . "\n\t"; |
||
| 104 | $conf .= 'same => n,Set(pl=${IF($["${CHANNEL:-1}" == "1"]?2:1)})' . "\n\t"; |
||
| 105 | $conf .= 'same => n,Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)})' . "\n\t"; |
||
| 106 | $conf .= 'same => n,ExecIf($[ "${orign_chan}x" == "x" ]?Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},FROM_CHAN)}))' . "\n\t"; |
||
| 107 | $conf .= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN}x" != "x" ]?Set(__QUEUE_SRC_CHAN=${orign_chan}))' . "\n\t"; |
||
| 108 | $conf .= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN:0:5}" == "Local" ]?Set(__QUEUE_SRC_CHAN=${FROM_CHAN}))' . "\n\t"; |
||
| 109 | $conf .= 'same => n,ExecIf($[ "${FROM_CHAN}x" == "x" ]?Set(__FROM_CHAN=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)}))' . "\n\t"; |
||
| 110 | $conf .= 'same => n,return' . "\n\n"; |
||
| 111 | |||
| 112 | $conf .= '[playback]' . "\n"; |
||
| 113 | $conf .= 'exten => s,1,Playback(hello_demo,noanswer)' . "\n\t"; |
||
| 114 | $conf .= 'same => n,ExecIf($["${SRC_BRIDGE_CHAN}x" == "x"]?Wait(30))' . "\n\t"; |
||
| 115 | $conf .= 'same => n,Wait(0.3)' . "\n\t"; |
||
| 116 | $conf .= 'same => n,Bridge(${SRC_BRIDGE_CHAN},kKTthH)' . "\n\n"; |
||
| 117 | |||
| 118 | $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n"; |
||
| 119 | |||
| 120 | // TODO / Добавление / удаление префиксов на входящий callerid. |
||
| 121 | $conf .= '[add-trim-prefix-clid]' . "\n"; |
||
| 122 | $conf .= 'exten => _.!,1,NoOp(--- Incoming call from ${CALLERID(num)} ---)' . "\n\t"; |
||
| 123 | $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t"; |
||
| 124 | // Отсекаем "+". |
||
| 125 | // $conf.= 'same => n,ExecIf( $["${CALLERID(num):0:1}" == "+"]?Set(CALLERID(num)=${CALLERID(num):1}))'."\n\t"; |
||
| 126 | // Отсекаем "7" и добавляем "8". |
||
| 127 | // $conf.= 'same => n,ExecIf( $["${REGEX("^7[0-9]+" ${CALLERID(num)})}" == "1"]?Set(CALLERID(num)=8${CALLERID(num):1}))'."\n\t"; |
||
| 128 | $conf .= 'same => n,return' . "\n\n"; |
||
| 129 | } |
||
| 130 | |||
| 131 | /** |
||
| 132 | * Генератор контекста для внутренних вызовов. |
||
| 133 | * |
||
| 134 | * @param $conf |
||
| 135 | */ |
||
| 136 | private function generateInternal(&$conf): void |
||
| 137 | { |
||
| 138 | $extension = 'X!'; |
||
| 139 | $technology = SIPConf::getTechnology(); |
||
| 140 | |||
| 141 | $additionalModules = $this->di->getShared('pbxConfModules'); |
||
| 142 | foreach ($additionalModules as $appClass) { |
||
| 143 | $addition = $appClass->extensionGenContexts(); |
||
| 144 | if (!empty($addition)){ |
||
| 145 | $conf .=$appClass->confBlockWithComments($addition); |
||
| 146 | } |
||
| 147 | } |
||
| 148 | $conf .= "\n"; |
||
| 149 | $conf .= "[internal-num-undefined] \n"; |
||
| 150 | $conf .= 'exten => _' . $extension . ',1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t"; |
||
| 151 | $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . "\n\t"; |
||
| 152 | $conf .= "same => n,Playback(pbx-invalid,noanswer) \n\n"; |
||
| 153 | |||
| 154 | $conf .= "[internal-fw]\n"; |
||
| 155 | $conf .= 'exten => _' . $extension . ',1,NoOp(DIALSTATUS - ${DIALSTATUS})' . "\n\t"; |
||
| 156 | // CANCEL - вызов был отменен, к примеру *0, не нужно дальше искать адресат. |
||
| 157 | $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "CANCEL"]?Hangup())' . "\n\t"; |
||
| 158 | // BUSY - занято. К примру абонент завершил вызов или DND. |
||
| 159 | $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "BUSY"]?Set(dstatus=FW_BUSY))' . "\n\t"; |
||
| 160 | // CHANUNAVAIL - канал не доступен. К примеру телефон не зарегистрирован или не отвечает. |
||
| 161 | $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "CHANUNAVAIL"]?Set(dstatus=FW_UNAV))' . "\n\t"; |
||
| 162 | // NOANSWER - не ответили по таймауту. |
||
| 163 | $conf .= 'same => n,ExecIf($["${dstatus}x" == "x"]?Set(dstatus=FW))' . "\n\t"; |
||
| 164 | $conf .= 'same => n,Set(fw=${DB(${dstatus}/${EXTEN})})' . "\n\t"; |
||
| 165 | $conf .= 'same => n,ExecIf($["${fw}x" != "x"]?Set(__pt1c_UNIQUEID=${UNDEFINED})' . "\n\t"; |
||
| 166 | $conf .= 'same => n,ExecIf($["${fw}x" != "x"]?Goto(internal,${fw},1))' . "\n\t"; |
||
| 167 | $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . "\n\t"; |
||
| 168 | $conf .= 'same => n,Hangup() ' . "\n\n"; |
||
| 169 | |||
| 170 | $conf .= "[all_peers]\n"; |
||
| 171 | $conf .= 'include => internal-hints' . "\n"; |
||
| 172 | $conf .= 'exten => failed,1,Hangup()' . "\n"; |
||
| 173 | |||
| 174 | $conf .= 'exten => _.!,1,ExecIf($[ "${EXTEN}" == "h" ]?Hangup())' . "\n\t"; |
||
| 175 | // Фильтр спецсимволов. Разершаем только цифры. |
||
| 176 | $conf .= 'same => n,Set(cleanNumber=${FILTER(\*\#1234567890,${EXTEN})})' . "\n\t"; |
||
| 177 | $conf .= 'same => n,ExecIf($["${EXTEN}" != "${cleanNumber}"]?Goto(${CONTEXT},${cleanNumber},$[${PRIORITY} + 1]))' . "\n\t"; |
||
| 178 | |||
| 179 | $conf .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t"; |
||
| 180 | $conf .= 'same => n,ExecIf($["${OLD_LINKEDID}x" == "x"]?Set(__OLD_LINKEDID=${CDR(linkedid)}))' . "\n\t"; |
||
| 181 | $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t"; |
||
| 182 | $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))' . "\n\t"; |
||
| 183 | |||
| 184 | $conf .= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(num)=${FROM_PEER}))' . "\n\t"; |
||
| 185 | $conf .= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(name)=${FROM_PEER}))' . "\n\t"; |
||
| 186 | |||
| 187 | $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local" && "${FROM_PEER}x" == "x"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t"; |
||
| 188 | $conf .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t"; |
||
| 189 | $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t"; |
||
| 190 | |||
| 191 | // Описываем возможность прыжка в пользовательский sub контекст. |
||
| 192 | $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t"; |
||
| 193 | |||
| 194 | $conf .= 'same => n,Goto(peer_${FROM_PEER},${EXTEN},1)' . "\n\n"; |
||
| 195 | |||
| 196 | $pickupexten = $this->generalSettings['PickupExten']; |
||
| 197 | $conf .= 'exten => _' . $pickupexten . $extension . ',1,Set(PICKUPEER=' . $technology . '/${FILTER(0-9,${EXTEN:2})})' . "\n\t"; |
||
| 198 | $conf .= 'same => n,Set(pt1c_dnid=${EXTEN})' . "\n\t"; |
||
| 199 | $conf .= 'same => n,PickupChan(${PICKUPEER})' . "\n\t"; |
||
| 200 | $conf .= 'same => n,Hangup()' . "\n\n"; |
||
| 201 | |||
| 202 | $voicemail_exten = $this->generalSettings['VoicemailExten']; |
||
| 203 | $conf .= 'exten => ' . $voicemail_exten . ',1,NoOp(NOTICE, Dialing out from ${CALLERID(all)} to VoiceMail)' . "\n\t"; |
||
| 204 | $conf .= 'same => n,VoiceMailMain(admin@voicemailcontext,s)' . "\n\t"; |
||
| 205 | $conf .= 'same => n,Hangup()' . "\n\n"; |
||
| 206 | |||
| 207 | $conf .= "[voice_mail_peer] \n"; |
||
| 208 | $conf .= 'exten => voicemail,1,Answer()' . "\n\t"; |
||
| 209 | $conf .= 'same => n,VoiceMail(admin@voicemailcontext)' . "\n\t"; |
||
| 210 | $conf .= 'same => n,Hangup()' . "\n\n"; |
||
| 211 | |||
| 212 | // Контекст для внутренних вызовов. |
||
| 213 | $conf .= "[internal] \n"; |
||
| 214 | |||
| 215 | foreach ($additionalModules as $appClass) { |
||
| 216 | $addition = $appClass->getIncludeInternal(); |
||
| 217 | if (!empty($addition)){ |
||
| 218 | $conf .=$appClass->confBlockWithComments($addition); |
||
| 219 | } |
||
| 220 | } |
||
| 221 | |||
| 222 | foreach ($additionalModules as $appClass) { |
||
| 223 | $addition = $appClass->extensionGenInternal(); |
||
| 224 | if (!empty($addition)){ |
||
| 225 | $conf .=$appClass->confBlockWithComments($addition); |
||
| 226 | } |
||
| 227 | } |
||
| 228 | |||
| 229 | $conf .= 'exten => i,1,NoOp(-- INVALID NUMBER --)' . "\n\t"; |
||
| 230 | $conf .= 'same => n,Set(DIALSTATUS=INVALID_NUMBER)' . "\n\t"; |
||
| 231 | $conf .= 'same => n,Playback(privacy-incorrect,noanswer)' . "\n\t"; |
||
| 232 | $conf .= 'same => n,Hangup()' . "\n"; |
||
| 233 | |||
| 234 | $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n"; |
||
| 235 | |||
| 236 | $conf .= "[internal-incoming]\n"; |
||
| 237 | $conf .= 'exten => _.!,1,ExecIf($["${MASTER_CHANNEL(M_TIMEOUT)}x" != "x"]?Set(TIMEOUT(absolute)=${MASTER_CHANNEL(M_TIMEOUT)}))' . " \n\t"; |
||
| 238 | $conf .= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT_CHANNEL)=${CHANNEL})' . " \n\t"; |
||
| 239 | $conf .= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT)=${EMPTY_VAR})' . " \n\t"; |
||
| 240 | $conf .= 'same => n,Goto(internal,${EXTEN},1)' . " \n\n"; |
||
| 241 | |||
| 242 | $conf .= "[internal-users] \n"; |
||
| 243 | $conf .= 'exten => _' . $extension . ',1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . " \n\t"; |
||
| 244 | $conf .= 'same => n,ExecIf($["${ISTRANSFER}x" != "x"]?Set(SIPADDHEADER01=${EMPTY_VAR})' . " \n\t"; |
||
| 245 | $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))' . " \n\t"; |
||
| 246 | |||
| 247 | $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t"; |
||
| 248 | // Проверим, существует ли такой пир. |
||
| 249 | |||
| 250 | $conf .= 'same => n,ExecIf($["${PJSIP_ENDPOINT(${EXTEN},auth)}x" == "x"]?Goto(internal-num-undefined,${EXTEN},1))' . " \n\t"; |
||
| 251 | $conf .= 'same => n,ExecIf($["${DEVICE_STATE(' . $technology . '/${EXTEN})}" == "BUSY"]?Set(DIALSTATUS=BUSY))' . " \n\t"; |
||
| 252 | $conf .= 'same => n,GotoIf($["${DEVICE_STATE(' . $technology . '/${EXTEN})}" == "BUSY"]?fw_start)' . " \n\t"; |
||
| 253 | |||
| 254 | // Как долго звонить пиру. |
||
| 255 | $conf .= 'same => n,Set(ringlength=${DB(FW_TIME/${EXTEN})})' . " \n\t"; |
||
| 256 | $conf .= 'same => n,ExecIf($["${ringlength}x" == "x" || "${QUEUE_SRC_CHAN}x" != "x"]?Set(ringlength=600))' . " \n\t"; |
||
| 257 | |||
| 258 | $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1) ' . " \n\t"; |
||
| 259 | // Совершаем вызов пира. |
||
| 260 | $conf .= 'same => n,Set(DST_CONTACT=${PJSIP_DIAL_CONTACTS(${EXTEN})})' . " \n\t"; |
||
| 261 | $conf .= 'same => n,ExecIf($["${FIELDQTY(DST_CONTACT,&)}" != "1"]?Set(__PT1C_SIP_HEADER=${EMPTY_VAR}))' . " \n\t"; |
||
| 262 | $conf .= 'same => n,ExecIf($["${DST_CONTACT}x" != "x"]?Dial(${DST_CONTACT},${ringlength},cTtekKHhU(${ISTRANSFER}dial_answer)b(dial_create_chan,s,1)):Set(DIALSTATUS=CHANUNAVAIL))' . " \n\t"; |
||
| 263 | $conf .= 'same => n(fw_start),NoOp(dial_hangup)' . " \n\t"; |
||
| 264 | |||
| 265 | // QUEUE_SRC_CHAN - установлена, если вызов сервершен агенту очереди. |
||
| 266 | // Проверяем нужна ли переадресация |
||
| 267 | $expression = '$["${DIALSTATUS}" != "ANSWER" && "${QUEUE_SRC_CHAN}x" == "x"]'; |
||
| 268 | $conf .= 'same => n,ExecIf(' . $expression . '?Goto(internal-fw,${EXTEN},1))' . " \n\t"; |
||
| 269 | $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . " \n\t"; |
||
| 270 | $conf .= 'same => n,Hangup()' . "\n\n"; |
||
| 271 | |||
| 272 | $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n"; |
||
| 273 | } |
||
| 274 | |||
| 275 | /** |
||
| 276 | * Генератор контекста для переадресаций. |
||
| 277 | * |
||
| 278 | * @param $conf |
||
| 279 | */ |
||
| 280 | private function generateInternalTransfer(&$conf): void |
||
| 299 | } |
||
| 300 | |||
| 301 | /** |
||
| 302 | * Генератор хинтов SIP. |
||
| 303 | * |
||
| 304 | * @param $conf |
||
| 305 | */ |
||
| 306 | private function generateSipHints(&$conf): void |
||
| 307 | { |
||
| 308 | $additionalModules = $this->di->getShared('pbxConfModules'); |
||
| 309 | $conf .= "[internal-hints] \n"; |
||
| 310 | foreach ($additionalModules as $appClass) { |
||
| 311 | $addition = $appClass->extensionGenHints(); |
||
| 312 | if (!empty($addition)){ |
||
| 313 | $conf .=$appClass->confBlockWithComments($addition); |
||
| 314 | } |
||
| 315 | } |
||
| 316 | $conf .= "\n\n"; |
||
| 317 | } |
||
| 318 | |||
| 319 | /** |
||
| 320 | * Генератор исходящих контекстов. |
||
| 321 | * |
||
| 322 | * @param $conf |
||
| 323 | */ |
||
| 324 | private function generateOutContextPeers(&$conf): void |
||
| 408 | } |
||
| 409 | } |
||
| 410 | |||
| 411 | /** |
||
| 412 | * Генератор исходящего маршрута. |
||
| 413 | * |
||
| 414 | * @param $rout |
||
| 415 | * |
||
| 416 | * @return string |
||
| 417 | */ |
||
| 418 | private function generateOutgoingRegexPattern($rout): string |
||
| 433 | } |
||
| 434 | |||
| 435 | /** |
||
| 436 | * Контекст для входящих внешних звонков без авторизации. |
||
| 437 | * |
||
| 438 | * @param $conf |
||
| 439 | */ |
||
| 440 | public function generatePublicContext(&$conf): void |
||
| 460 | } |
||
| 461 | } |
||
| 462 | |||
| 463 | /** |
||
| 464 | * Генератор входящих контекстов. |
||
| 465 | * |
||
| 466 | * @param string | array $provider |
||
| 467 | * @param string | array $login |
||
| 468 | * @param string $uniqid |
||
| 469 | * |
||
| 470 | * @return string |
||
| 471 | */ |
||
| 472 | public static function generateIncomingContextPeers($provider, $login = '', $uniqid = ''): string |
||
| 473 | { |
||
| 474 | $conf = ''; |
||
| 475 | $dialplan = []; |
||
| 476 | $di = Di::getDefault(); |
||
| 477 | if ($di===null){ |
||
| 478 | return ''; |
||
| 479 | } |
||
| 480 | $additionalModules = $di->getShared('pbxConfModules'); |
||
| 481 | |||
| 482 | if ('none' === $provider) { |
||
| 483 | // Звонки по sip uri. |
||
| 484 | $filter = [ |
||
| 485 | 'provider IS NULL AND priority<>9999', |
||
| 486 | 'order' => 'provider,priority,extension', |
||
| 487 | ]; |
||
| 488 | } elseif (is_array($provider)) { |
||
| 489 | $filter = [ |
||
| 490 | 'provider IN ({provider:array})', |
||
| 491 | 'bind' => [ |
||
| 492 | 'provider' => array_keys($provider), |
||
| 493 | ], |
||
| 494 | 'order' => 'provider,priority,extension', |
||
| 495 | ]; |
||
| 496 | } else { |
||
| 497 | // Звонки через провайдера. |
||
| 498 | $filter = [ |
||
| 499 | "provider = '$provider'", |
||
| 500 | 'order' => 'provider,priority,extension', |
||
| 501 | ]; |
||
| 502 | } |
||
| 503 | /** @var \MikoPBX\Common\Models\IncomingRoutingTable $default_action */ |
||
| 504 | $default_action = IncomingRoutingTable::findFirst('priority = 9999'); |
||
| 505 | /** @var \MikoPBX\Common\Models\IncomingRoutingTable $m_data */ |
||
| 506 | $m_data = IncomingRoutingTable::find($filter); |
||
| 507 | $data = $m_data->toArray(); |
||
| 508 | |||
| 509 | $need_def_rout = true; |
||
| 510 | foreach ($data as $rout) { |
||
| 511 | $number = trim($rout['number']); |
||
| 512 | if ($number === 'X!' || $number === '') { |
||
| 513 | $need_def_rout = false; |
||
| 514 | break; |
||
| 515 | } |
||
| 516 | } |
||
| 517 | if ($need_def_rout === true && 'none' !== $provider) { |
||
| 518 | $data[] = ['number' => '', 'extension' => '', 'timeout' => '']; |
||
| 519 | } |
||
| 520 | $config = new MikoPBXConfig(); |
||
| 521 | $lang = str_replace('_', '-', $config->getGeneralSettings('PBXLanguage')); |
||
| 522 | |||
| 523 | $rout_data_dial = []; |
||
| 524 | foreach ($data as $rout) { |
||
| 525 | $number = trim($rout['number']); |
||
| 526 | $timeout = trim($rout['timeout']); |
||
| 527 | $rout_number = ($number === '') ? 'X!' : $number; |
||
| 528 | $rout_data = &$dialplan[$rout_number]; |
||
| 529 | if (empty($rout_data)) { |
||
| 530 | $ext_prefix = ('none' === $provider) ? '' : '_'; |
||
| 531 | $rout_data .= "exten => {$ext_prefix}{$rout_number},1,NoOp(--- Incoming call ---)\n\t"; |
||
| 532 | $rout_data .= 'same => n,Set(CHANNEL(language)=' . $lang . ')' . "\n\t"; |
||
| 533 | $rout_data .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t"; |
||
| 534 | $rout_data .= 'same => n,Set(__FROM_DID=${EXTEN})' . "\n\t"; |
||
| 535 | $rout_data .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t"; |
||
| 536 | |||
| 537 | // Установка имени пира. |
||
| 538 | $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t"; |
||
| 539 | $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t"; |
||
| 540 | |||
| 541 | // TODO / Подмена входящего callerid. |
||
| 542 | $rout_data .= 'same => n,Gosub(add-trim-prefix-clid,${EXTEN},1)' . "\n\t"; |
||
| 543 | |||
| 544 | foreach ($additionalModules as $appClass) { |
||
| 545 | $addition = $appClass->generateIncomingRoutBeforeDial($rout_number); |
||
| 546 | if (!empty($addition)){ |
||
| 547 | $rout_data .=$appClass->confBlockWithComments($addition); |
||
| 548 | } |
||
| 549 | } |
||
| 550 | |||
| 551 | // Перехват на ответственного. |
||
| 552 | $rout_data .= 'same => n,UserEvent(Interception,CALLERID: ${CALLERID(num)},chan1c: ${CHANNEL},FROM_DID: ${FROM_DID})' . "\n\t"; |
||
| 553 | // Проверим распискние для входящих внешних звонков. |
||
| 554 | $rout_data .= 'same => n,Gosub(check-out-work-time,${EXTEN},1)'; |
||
| 555 | // Описываем возможность прыжка в пользовательский sub контекст. |
||
| 556 | $rout_data .= " \n\t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)'; |
||
| 557 | } |
||
| 558 | |||
| 559 | if ( ! empty($rout['extension'])) { |
||
| 560 | $rout_data = rtrim($rout_data); |
||
| 561 | // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо. |
||
| 562 | // При ответе может отработать следующий приоритет. |
||
| 563 | if ( ! isset($rout_data_dial[$rout_number])) { |
||
| 564 | $rout_data_dial[$rout_number] = ''; |
||
| 565 | } |
||
| 566 | |||
| 567 | $dial_command = " \n\t" . 'same => n,' . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$rout['extension']}@internal-incoming/n,{$timeout},cTKg));"; |
||
| 568 | $rout_data_dial[$rout_number] .= " \n\t" . "same => n,Set(M_TIMEOUT={$timeout})"; |
||
| 569 | $rout_data_dial[$rout_number] .= $dial_command; |
||
| 570 | |||
| 571 | if (is_array($provider)) { |
||
| 572 | $key = $provider[$rout['provider']] ?? ''; |
||
| 573 | if ( ! isset($rout_data_dial[$key])) { |
||
| 574 | $rout_data_dial[$key] = ''; |
||
| 575 | } |
||
| 576 | if (empty($number)) { |
||
| 577 | $rout_data_dial[$key] .= $dial_command; |
||
| 578 | } |
||
| 579 | } |
||
| 580 | } |
||
| 581 | } |
||
| 582 | |||
| 583 | if (is_string($login)) { |
||
| 584 | $add_login_pattern = ! empty($login); |
||
| 585 | foreach ($data as $rout) { |
||
| 586 | if ( ! $add_login_pattern) { |
||
| 587 | break; |
||
| 588 | } // Логин не заполнен, обработка не требуется. |
||
| 589 | $is_num = preg_match_all('/^\d+$/m', $login, $matches, PREG_SET_ORDER, 0); |
||
| 590 | if ($is_num === 1) { |
||
| 591 | // Это числовой номер, потому, не требуется дополнительно описывать exten. |
||
| 592 | $add_login_pattern = false; |
||
| 593 | break; |
||
| 594 | } |
||
| 595 | if (trim($rout['number']) !== $login) { |
||
| 596 | // Совпадение exten не найдено. Идем дальше. |
||
| 597 | continue; |
||
| 598 | } |
||
| 599 | // Совпадение найдено, не требуется дополнительно описывать exten. |
||
| 600 | $add_login_pattern = false; |
||
| 601 | break; |
||
| 602 | } |
||
| 603 | if ($add_login_pattern && array_key_exists('X!', $rout_data_dial) && isset($dialplan['X!'])) { |
||
| 604 | $dialplan[$login] = str_replace('_X!,1', "{$login},1", $dialplan['X!']); |
||
| 605 | $rout_data_dial[$login] = $rout_data_dial['X!']; |
||
| 606 | }elseif($add_login_pattern === true && $need_def_rout === true && count($data) === 1){ |
||
| 607 | // Только маршрут "По умолчанию". |
||
| 608 | $dialplan[$login] = str_replace('_X!,1', "{$login},1", $dialplan['X!']); |
||
| 609 | } |
||
| 610 | } elseif (is_array($provider)) { |
||
| 611 | foreach (array_values($provider) as $_login) { |
||
| 612 | $dialplan[$_login] = str_replace('_X!,1', "{$_login},1", $dialplan['X!']); |
||
| 613 | } |
||
| 614 | } |
||
| 615 | |||
| 616 | foreach ($dialplan as $key => &$dpln) { |
||
| 617 | if ( ! array_key_exists($key, $rout_data_dial)) { |
||
| 618 | continue; |
||
| 619 | } |
||
| 620 | $dpln = rtrim($dpln); |
||
| 621 | $dpln .= $rout_data_dial[$key]; |
||
| 622 | } |
||
| 623 | unset($dpln); |
||
| 624 | |||
| 625 | $uniqid = is_string($provider) ? $provider : $uniqid; |
||
| 626 | $conf .= "\n" . "[{$uniqid}-incoming]\n"; |
||
| 627 | foreach ($dialplan as $dpln) { |
||
| 628 | $conf .= $dpln . "\n"; |
||
| 629 | if (null == $default_action && 'none' !== $provider) { |
||
| 630 | continue; |
||
| 631 | } |
||
| 632 | if ('extension' === $default_action->action) { |
||
| 633 | // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо. |
||
| 634 | // При ответе может отработать следующий приоритет. |
||
| 635 | $conf .= "\t" . 'same => n,Set(M_TIMEOUT=0)' . "\n"; |
||
| 636 | $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$default_action->extension}@internal/n,,cTKg)); default action" . "\n"; |
||
| 637 | |||
| 638 | foreach ($additionalModules as $appClass) { |
||
| 639 | $addition = $appClass->generateIncomingRoutAfterDialContext($uniqid); |
||
| 640 | if (!empty($addition)){ |
||
| 641 | $conf .=$appClass->confBlockWithComments($addition); |
||
| 642 | } |
||
| 643 | } |
||
| 644 | $conf .= " \t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-after-dial-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-after-dial-custom,${EXTEN},1)' . "\n"; |
||
| 645 | } elseif ('busy' === $default_action->action) { |
||
| 646 | $conf .= "\t" . "same => n,Busy()" . "\n"; |
||
| 647 | } |
||
| 648 | $conf .= "\t" . "same => n,Hangup()" . "\n"; |
||
| 649 | } |
||
| 650 | |||
| 651 | return $conf; |
||
| 652 | } |
||
| 653 | |||
| 654 | /** |
||
| 655 | * Описываем нерабочее время. |
||
| 656 | * |
||
| 657 | * @param $conf |
||
| 658 | * |
||
| 659 | * @return string |
||
| 660 | */ |
||
| 661 | private function generateOutWorkTimes(&$conf): string |
||
| 662 | { |
||
| 663 | $conf .= "\n\n[playback-exit]\n"; |
||
| 664 | $conf .= 'exten => _.!,1,NoOp(check time)' . "\n\t"; |
||
| 665 | $conf .= 'same => n,Gosub(dial_outworktimes,${EXTEN},1)' . "\n\t"; |
||
| 666 | $conf .= 'same => n,Playback(${filename})' . "\n\t"; |
||
| 667 | $conf .= 'same => n,Hangup()' . "\n\n"; |
||
| 668 | |||
| 669 | $conf .= "[check-out-work-time]\n"; |
||
| 670 | $conf .= "exten => _.!,1,NoOp(check time)\n\t"; |
||
| 671 | |||
| 672 | $data = OutWorkTimes::find(['order' => 'date_from']); |
||
| 673 | |||
| 674 | $conf_out_set_var = ''; |
||
| 675 | |||
| 676 | $now_year = 1 * date('Y', time()); |
||
| 677 | foreach ($data as $out_data) { |
||
| 678 | /** @var \MikoPBX\Common\Models\OutWorkTimes $out_data */ |
||
| 679 | if ( ! empty($out_data->date_from) && ! empty($out_data->date_to)) { |
||
| 680 | $year_from = 1 * date('Y', (int)$out_data->date_from); |
||
| 681 | $year_to = 1 * date('Y', (int)$out_data->date_to); |
||
| 682 | if ($now_year < $year_from || $now_year > $year_to) { |
||
| 683 | // Правило не актуально для текущего года. |
||
| 684 | continue; |
||
| 685 | } |
||
| 686 | } |
||
| 687 | $time_from = $out_data->time_from; |
||
| 688 | $time_to = $out_data->time_to; |
||
| 689 | if (empty($time_from) && empty($time_to)) { |
||
| 690 | $times = '*'; |
||
| 691 | } else { |
||
| 692 | $time_to = (empty($time_to)) ? '23:59' : $time_to; |
||
| 693 | $time_to = (strlen($time_to) === 4) ? "0{$time_to}" : $time_to; |
||
| 694 | |||
| 695 | $time_from = (empty($time_from)) ? '00:00' : $time_from; |
||
| 696 | $time_from = (strlen($time_from) === 4) ? "0{$time_from}" : $time_from; |
||
| 697 | |||
| 698 | $times = "{$time_from}-{$time_to}"; |
||
| 699 | } |
||
| 700 | |||
| 701 | $weekday_from = (string) $out_data->weekday_from; |
||
| 702 | $weekday_to = (string) $out_data->weekday_to; |
||
| 703 | $arr_weekday = [null, "mon", "tue", "wed", "thu", "fri", "sat", "sun"]; |
||
| 704 | if (empty($weekday_from) && empty($weekday_to)) { |
||
| 705 | $weekdays = '*'; |
||
| 706 | } else { |
||
| 707 | $weekday_from = (empty($weekday_from)) ? '1' : $weekday_from; |
||
| 708 | $weekday_to = (empty($weekday_to)) ? '7' : $weekday_to; |
||
| 709 | $weekdays = "{$arr_weekday[$weekday_from]}-{$arr_weekday[$weekday_to]}"; |
||
| 710 | } |
||
| 711 | |||
| 712 | $date_from = $out_data->date_from; |
||
| 713 | $date_to = $out_data->date_to; |
||
| 714 | if (empty($date_from)) { |
||
| 715 | $mdays = '*'; |
||
| 716 | $months = '*'; |
||
| 717 | } else { |
||
| 718 | $mdays = strtolower(date("j", (int)$date_from)); |
||
| 719 | $months = strtolower(date("M", (int)$date_from)); |
||
| 720 | if ( ! empty($date_to)) { |
||
| 721 | $mdays .= "-" . strtolower(date("j", (int)$date_to)); |
||
| 722 | $months .= "-" . strtolower(date("M", (int)$date_to)); |
||
| 723 | } |
||
| 724 | } |
||
| 725 | |||
| 726 | |||
| 727 | if ('extension' === $out_data->action) { |
||
| 728 | $appname = 'GotoIfTime'; |
||
| 729 | $appdata = "internal,{$out_data->extension},1"; |
||
| 730 | } else { |
||
| 731 | /** @var \MikoPBX\Common\Models\SoundFiles $res */ |
||
| 732 | $res = SoundFiles::findFirst($out_data->audio_message_id); |
||
| 733 | $audio_message = ($res === null) ? '' : Util::trimExtensionForFile($res->path); |
||
| 734 | |||
| 735 | $conf_out_set_var .= "[work-time-set-var-{$out_data->id}]\n" . |
||
| 736 | 'exten => _.!,1,Set(filename=' . $audio_message . ')' . "\n\t" . |
||
| 737 | 'same => n,Goto(playback-exit,${EXTEN},1)' . "\n\n"; |
||
| 738 | |||
| 739 | $appname = 'ExecIfTime'; |
||
| 740 | $appdata = 'Goto(work-time-set-var-' . $out_data->id . ',${EXTEN},1)'; |
||
| 741 | } |
||
| 742 | $conf .= "same => n,{$appname}($times,$weekdays,$mdays,$months?{$appdata})\n\t"; |
||
| 743 | } |
||
| 744 | |||
| 745 | $conf .= "same => n,return\n\n"; |
||
| 746 | |||
| 747 | $conf .= $conf_out_set_var; |
||
| 748 | |||
| 749 | return $conf; |
||
| 750 | } |
||
| 751 | |||
| 752 | /** |
||
| 753 | * Генератор extension для контекста outgoing. |
||
| 754 | * |
||
| 755 | * @param string $uniqueID |
||
| 756 | * |
||
| 757 | * @return null|string |
||
| 758 | */ |
||
| 759 | public function getTechByID(string $uniqueID): string |
||
| 774 | } |
||
| 775 | } |