1
1
<?php
2
- /**
3
- * Copyright © MIKO LLC - All Rights Reserved
4
- * Unauthorized copying of this file, via any medium is strictly prohibited
5
- * Proprietary and confidential
6
- * Written by Alexey Portnov, 11 2018
2
+
3
+ /*
4
+ * MikoPBX - free phone system for small business
5
+ * Copyright © 2017-2024 Alexey Portnov and Nikolay Beketov
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation; either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License along with this program.
18
+ * If not, see <https://www.gnu.org/licenses/>.
7
19
*/
8
20
9
21
namespace Modules \ModulePhoneBook \App \Controllers ;
10
22
11
- use MikoPBX \Modules \PbxExtensionUtils ;
12
- use Modules \ModulePhoneBook \Models \PhoneBook ;
13
23
use MikoPBX \AdminCabinet \Controllers \BaseController ;
24
+ use MikoPBX \AdminCabinet \Providers \AssetProvider ;
25
+ use Modules \ModulePhoneBook \App \Forms \ModuleConfigForm ;
26
+ use Modules \ModulePhoneBook \Models \PhoneBook ;
27
+ use Modules \ModulePhoneBook \Models \Settings ;
14
28
15
29
class ModulePhoneBookController extends BaseController
16
30
{
17
-
18
31
private $ moduleUniqueID = 'ModulePhoneBook ' ;
19
- private $ moduleDir ;
20
32
21
- /**
22
- * Basic initial class
23
- */
24
- public function initialize (): void
25
- {
26
- $ this ->moduleDir = PbxExtensionUtils::getModuleDir ($ this ->moduleUniqueID );
27
- if ($ this ->request ->isAjax () === false ) {
28
- $ this ->view ->logoImagePath = "{$ this ->url ->get ()}assets/img/cache/ {$ this ->moduleUniqueID }/logo.svg " ;
29
- $ this ->view ->submitMode = null ;
30
- }
31
- parent ::initialize ();
32
- }
33
+ public bool $ showModuleStatusToggle = false ;
33
34
34
35
/**
35
- * Index page controller
36
+ * Controller for the index page.
36
37
*/
37
38
public function indexAction (): void
38
39
{
39
- $ headerCollectionCSS = $ this ->assets ->collection ('headerCSS ' );
40
+ $ this ->view ->logoImagePath = "{$ this ->url ->get ()}assets/img/cache/ {$ this ->moduleUniqueID }/logo.svg " ;
41
+ $ this ->view ->submitMode = null ;
42
+
43
+ // Add necessary CSS files
44
+ $ headerCollectionCSS = $ this ->assets ->collection (AssetProvider::HEADER_CSS );
40
45
$ headerCollectionCSS
41
46
->addCss ('css/vendor/datatable/dataTables.semanticui.min.css ' , true )
42
47
->addCss ("css/cache/ {$ this ->moduleUniqueID }/module-phonebook.css " , true );
43
48
44
- $ footerCollection = $ this ->assets ->collection ('footerJS ' );
49
+ // Add Semantic UI modal CSS
50
+ $ semanticCollectionCSS = $ this ->assets ->collection (AssetProvider::SEMANTIC_UI_CSS );
51
+ $ semanticCollectionCSS
52
+ ->addCss ('css/vendor/semantic/progress.min.css ' , true )
53
+ ->addCss ('css/vendor/semantic/modal.min.css ' , true );
54
+
55
+ // Add Semantic UI modal JS
56
+ $ semanticCollectionJS = $ this ->assets ->collection (AssetProvider::SEMANTIC_UI_JS );
57
+ $ semanticCollectionJS
58
+ ->addJs ('js/vendor/semantic/progress.min.js ' , true )
59
+ ->addJs ('js/vendor/semantic/modal.min.js ' , true );
60
+
61
+ // Add JS files required for this page
62
+ $ footerCollection = $ this ->assets ->collection (AssetProvider::FOOTER_JS );
45
63
$ footerCollection
46
64
->addJs ('js/vendor/inputmask/inputmask.js ' , true )
47
65
->addJs ('js/vendor/inputmask/jquery.inputmask.js ' , true )
48
66
->addJs ('js/vendor/inputmask/jquery.inputmask-multi.js ' , true )
49
67
->addJs ('js/vendor/inputmask/bindings/inputmask.binding.js ' , true )
50
68
->addJs ('js/vendor/datatable/dataTables.semanticui.js ' , true )
51
69
->addJs ('js/pbx/Extensions/input-mask-patterns.js ' , true )
52
- ->addJs ("js/cache/ {$ this ->moduleUniqueID }/module-phonebook-index.js " , true );
70
+ ->addJs ('js/vendor/resumable.js ' , true )
71
+ ->addJs ("js/cache/ {$ this ->moduleUniqueID }/module-phonebook-status.js " , true )
72
+ ->addJs ("js/cache/ {$ this ->moduleUniqueID }/module-phonebook-index.js " , true )
73
+ ->addJs ("js/cache/ {$ this ->moduleUniqueID }/module-phonebook-settings.js " , true )
74
+ ->addJs ("js/cache/ {$ this ->moduleUniqueID }/module-phonebook-merging-worker.js " , true )
75
+ ->addJs ("js/cache/ {$ this ->moduleUniqueID }/module-phonebook-import.js " , true )
76
+ ->addJs ("js/cache/ {$ this ->moduleUniqueID }/module-phonebook-datatable.js " , true );
77
+
78
+ $ settings = Settings::findFirst ();
79
+ if ($ settings === null ) {
80
+ $ settings = new Settings ();
81
+ $ settings ->disableInputMask = '0 ' ;
82
+ }
53
83
54
- $ this ->view ->pick ( "{ $ this -> moduleDir } /App/Views/index " );
84
+ $ this ->view ->form = new ModuleConfigForm ( $ settings );
55
85
}
56
86
57
87
/**
58
- * Запрос нового пакета истории разговоров для DataTable JSON
88
+ * Request new call history records for DataTable in JSON format.
59
89
*/
60
90
public function getNewRecordsAction (): void
61
91
{
62
- $ currentPage = $ this ->request ->getPost ('draw ' );
63
- $ position = $ this ->request ->getPost ('start ' );
64
- $ recordsPerPage = $ this ->request ->getPost ('length ' );
65
- $ searchPhrase = $ this ->request ->getPost ('search ' );
66
- $ this ->view ->draw = $ currentPage ;
67
- $ this ->view ->recordsTotal = 0 ;
92
+ $ currentPage = $ this ->request ->getPost ('draw ' );
93
+ $ position = $ this ->request ->getPost ('start ' );
94
+ $ recordsPerPage = $ this ->request ->getPost ('length ' );
95
+ $ searchPhrase = $ this ->request ->getPost ('search ' );
96
+
97
+ $ this ->view ->draw = $ currentPage ;
98
+ $ this ->view ->recordsTotal = 0 ;
68
99
$ this ->view ->recordsFiltered = 0 ;
69
- $ this ->view ->data = [];
100
+ $ this ->view ->data = [];
70
101
71
- // Посчитаем количество уникальных записей в таблице телефонов
102
+ // Count the total number of unique records in the phonebook table
72
103
$ parameters ['columns ' ] = 'COUNT(*) as rows ' ;
73
- $ recordsTotalReq = PhoneBook::findFirst ($ parameters );
104
+ $ recordsTotalReq = PhoneBook::findFirst ($ parameters );
74
105
if ($ recordsTotalReq !== null ) {
75
- $ recordsTotal = $ recordsTotalReq ->rows ;
106
+ $ recordsTotal = $ recordsTotalReq ->rows ;
76
107
$ this ->view ->recordsTotal = $ recordsTotal ;
77
108
} else {
78
109
return ;
79
110
}
80
- // Посчитаем количество записей с учетом фильтра
81
- if ( ! empty ($ searchPhrase ['value ' ])) {
111
+
112
+ // Count the number of records based on the search filter
113
+ if (!empty ($ searchPhrase ['value ' ])) {
82
114
$ this ->prepareConditionsForSearchPhrases ($ searchPhrase ['value ' ], $ parameters );
83
- // Если мы не смогли расшифровать строку запроса вернем пустой результата
84
- if (empty ($ parameters ['conditions ' ])) {
85
- return ;
86
- }
87
115
}
88
116
$ recordsFilteredReq = PhoneBook::findFirst ($ parameters );
89
117
if ($ recordsFilteredReq !== null ) {
90
- $ recordsFiltered = $ recordsFilteredReq ->rows ;
118
+ $ recordsFiltered = $ recordsFilteredReq ->rows ;
91
119
$ this ->view ->recordsFiltered = $ recordsFiltered ;
92
120
}
93
121
94
- // Найдем все записи подходящие под заданный фильтр
122
+ // Retrieve all records that match the filter criteria
95
123
$ parameters ['columns ' ] = [
96
124
'call_id ' ,
97
- 'number ' => 'number_rep ' ,
125
+ 'number ' => 'number_rep ' ,
98
126
'DT_RowId ' => 'id ' ,
99
127
];
100
- $ parameters ['order ' ] = ['call_id desc ' ];
101
- $ parameters ['limit ' ] = $ recordsPerPage ;
102
- $ parameters ['offset ' ] = $ position ;
103
- $ records = PhoneBook::find ($ parameters );
104
- $ this ->view ->data = $ records ->toArray ();
105
- }
128
+ $ parameters ['order ' ] = ['call_id desc ' ];
129
+ $ parameters ['limit ' ] = $ recordsPerPage ;
130
+ $ parameters ['offset ' ] = $ position ;
106
131
132
+ $ records = PhoneBook::find ($ parameters );
133
+ $ this ->view ->data = $ records ->toArray ();
134
+ }
107
135
108
136
/**
109
- * Подготовка параметров запроса для фильтрации записей телефонной книги
137
+ * Prepare search conditions for filtering phonebook records.
110
138
*
111
- * @param $searchPhrase - поисковая фраза, которую ввел пользователь
112
- * @param $parameters - параметры запроса к телефонной кнгие
139
+ * @param string $searchPhrase The search phrase entered by the user
140
+ * @param array $parameters The query parameters to filter the phonebook records
113
141
*/
114
- private function prepareConditionsForSearchPhrases (&$ searchPhrase , &$ parameters ): void
142
+ private function prepareConditionsForSearchPhrases (string &$ searchPhrase , array &$ parameters ): void
115
143
{
116
- $ parameters ['conditions ' ] = '' ;
117
-
118
- // Поищем номер телефона
119
- $ searchPhrase = str_replace (['( ' , ') ' , '- ' , '+ ' ], '' , $ searchPhrase );
120
- if (preg_match_all ("/\d+/ " , $ searchPhrase , $ matches )) {
121
- if (count ($ matches [0 ]) === 1 ) {
122
- $ seekNumber = '1 ' .substr ($ matches [0 ][0 ], -9 );
123
- $ parameters ['conditions ' ] = 'number LIKE :SearchPhrase: ' ;
124
- $ parameters ['bind ' ]['SearchPhrase ' ] = "% {$ seekNumber }% " ;
125
- }
126
- $ searchPhrase = str_replace ($ matches [0 ][0 ], '' , $ searchPhrase );
127
- }
128
-
129
- // Ищем по caller_id
130
- if (preg_match_all ('/^([а-яА-ЯЁёa-zA-Z0-9_ ]+)$/u ' , $ searchPhrase , $ matches ) && count ($ matches [0 ]) > 0 ) {
131
- $ parameters ['conditions ' ] = 'call_id like :SearchPhrase1: ' ;
132
- $ parameters ['bind ' ]['SearchPhrase1 ' ] = "% {$ matches [0 ][0 ]}% " ;
133
- }
144
+ $ parameters ['conditions ' ] = 'search_index like :SearchPhrase: ' ;
145
+ $ parameters ['bind ' ]['SearchPhrase ' ] = "% $ searchPhrase% " ;
134
146
}
135
147
136
148
/**
137
- * Save settings AJAX action
149
+ * Save settings via an AJAX request.
138
150
*/
139
151
public function saveAction (): void
140
152
{
141
- if ( ! $ this ->request ->isPost ()) {
153
+ if (! $ this ->request ->isPost ()) {
142
154
return ;
143
155
}
144
- $ data = $ this ->request ->getPost ();
145
156
146
- if (empty ($ data ['call_id ' ]) || empty ($ data ['number ' ])) {
157
+ $ dataId = $ this ->request ->getPost ('id ' , ['string ' , 'trim ' ]);
158
+ $ callId = $ this ->request ->getPost ('call_id ' , ['string ' , 'trim ' ]);
159
+ $ number = $ this ->request ->getPost ('number ' , ['alnum ' ]);
160
+ $ numberRep = $ this ->request ->getPost ('number_rep ' , ['string ' , 'trim ' ], $ number );
161
+
162
+ if (empty ($ callId ) || empty ($ number )) {
147
163
return ;
148
164
}
149
165
150
- // We haven't ability to change primary filed, we have to delete it and recreate
166
+ // If we are unable to change the primary field, delete the old record and recreate it
151
167
$ oldId = null ;
152
168
$ record = null ;
153
- if (stripos ($ data [ ' id ' ] , 'new ' ) === false ) {
154
- $ record = PhoneBook::findFirstById ($ data [ ' id ' ] );
155
- if ($ record ->number !==$ data [ ' number ' ]) {
169
+ if (stripos ($ dataId , 'new ' ) === false ) {
170
+ $ record = PhoneBook::findFirstById ($ dataId );
171
+ if ($ record ->number !== $ number) {
156
172
$ oldId = $ record ->id ;
157
173
$ record ->delete ();
158
174
$ record = null ;
@@ -167,12 +183,23 @@ public function saveAction(): void
167
183
switch ($ key ) {
168
184
case 'id ' :
169
185
break ;
186
+ case 'number ' :
187
+ $ record ->number = $ number ;
188
+ break ;
189
+ case 'number_rep ' :
190
+ $ record ->number_rep = $ numberRep ;
191
+ break ;
192
+ case 'call_id ' :
193
+ $ record ->call_id = $ callId ;
194
+ break ;
195
+ case 'search_index ' :
196
+ // Collect data for the search index
197
+ $ username = mb_strtolower ($ callId );
198
+ // Combine all fields into a single string
199
+ $ record ->search_index = $ username . $ number . $ numberRep ;
200
+ break ;
170
201
default :
171
- if (array_key_exists ($ key , $ data )) {
172
- $ record ->$ key = $ data [$ key ];
173
- } else {
174
- $ record ->$ key = '' ;
175
- }
202
+ break ;
176
203
}
177
204
}
178
205
@@ -183,24 +210,64 @@ public function saveAction(): void
183
210
184
211
return ;
185
212
}
186
- $ this ->view ->data = ['oldId ' =>$ oldId ,'newId ' =>$ record ->id ];
213
+
214
+ $ this ->view ->data = ['oldId ' => $ oldId , 'newId ' => $ record ->id ];
187
215
$ this ->view ->success = true ;
188
216
}
189
217
190
218
/**
191
- * Delete phonebook record
219
+ * Delete a phonebook record.
192
220
*
193
- * @param string $id record ID
221
+ * @param string|null $id The record ID to delete
194
222
*/
195
- public function deleteAction ($ id = null ): void
223
+ public function deleteAction (? string $ id = null ): void
196
224
{
197
225
$ record = PhoneBook::findFirstById ($ id );
198
- if ($ record !== null && ! $ record ->delete ()) {
226
+ if ($ record !== null && !$ record ->delete ()) {
199
227
$ this ->flash ->error (implode ('<br> ' , $ record ->getMessages ()));
200
228
$ this ->view ->success = false ;
229
+ return ;
230
+ }
231
+ $ this ->view ->success = true ;
232
+ }
233
+
234
+ /**
235
+ * Delete all phonebook records.
236
+ */
237
+ public function deleteAllRecordsAction (): void
238
+ {
239
+ $ records = PhoneBook::find ();
240
+ foreach ($ records as $ record ) {
241
+ if (!$ record ->delete ()) {
242
+ $ this ->flash ->error (implode ('<br> ' , $ record ->getMessages ()));
243
+ $ this ->view ->result = false ;
244
+ return ;
245
+ }
246
+ }
247
+ $ this ->view ->result = true ;
248
+ $ this ->view ->reload = 'module-phone-book/module-phone-book/index ' ;
249
+ }
201
250
251
+ /**
252
+ * Toggle input mask feature.
253
+ */
254
+ public function toggleDisableInputMaskAction (): void
255
+ {
256
+ if (!$ this ->request ->isPost ()) {
257
+ return ;
258
+ }
259
+
260
+ $ settings = Settings::findFirst ();
261
+ if ($ settings === null ) {
262
+ $ settings = new Settings ();
263
+ }
264
+
265
+ $ settings ->disableInputMask = $ this ->request ->getPost ('disableInputMask ' ) === 'true ' ? '1 ' : '0 ' ;
266
+ if (!$ settings ->save ()) {
267
+ $ this ->flash ->error (implode ('<br> ' , $ settings ->getMessages ()));
268
+ $ this ->view ->success = false ;
202
269
return ;
203
270
}
204
271
$ this ->view ->success = true ;
205
272
}
206
- }
273
+ }
0 commit comments