| 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 | } |