api.modelcraft.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. <?php
  2. /**
  3. * ONVIF-explorer and custom device templates manager implementation
  4. */
  5. class ModelCraft {
  6. /**
  7. * ONVIF layer placeholder
  8. *
  9. * @var object
  10. */
  11. protected $onvif = '';
  12. /**
  13. * System messages helper instance placeholder
  14. *
  15. * @var object
  16. */
  17. protected $messages = '';
  18. /**
  19. * Custom device templates database abstraction layer
  20. *
  21. * @var object
  22. */
  23. protected $custTemplatesDb = '';
  24. /**
  25. * Contains all custom device templates data as id=>custtpldata
  26. *
  27. * @var array
  28. */
  29. protected $allCustTemplatesData = array();
  30. /**
  31. * some predefined stuff here
  32. */
  33. const TABLE_TEMPLATES = 'custtpls';
  34. const URL_ME = '?module=modelcraft';
  35. const ROUTE_EXPLORER = 'onvifexplorer';
  36. const PROUTE_EXPLORE = 'explorecamera';
  37. const PROUTE_EXPLORE_IP = 'exploredevip';
  38. const PROUTE_EXPLORE_LOGIN = 'exploredevlogin';
  39. const PROUTE_EXPLORE_PASSWORD = 'exploredevpassword';
  40. const PROUTE_TPLCREATE_DEV = 'customtemplatedevicename';
  41. const PROUTE_TPLCREATE_PROTO = 'customtemplateproto';
  42. const PROUTE_TPLCREATE_MAIN = 'customtemplatemainstream';
  43. const PROUTE_TPLCREATE_SUB = 'customtemplatesubstream';
  44. const PROUTE_TPLCREATE_PORT = 'customtemplateport';
  45. const PROUTE_TPLCREATE_SOUND = 'customtemplatesound';
  46. const PROUTE_TPLCREATE_PTZ = 'customtemplateptz';
  47. const ROUTE_TPL_DEL = 'deletecusttpl';
  48. const CUSTOM_TEMPLATE_MARK = '_CUSTOM';
  49. /**
  50. * Spirits gathering around my dead coil
  51. * Infernal baptism, hungry for blood
  52. */
  53. public function __construct() {
  54. $this->initMessages();
  55. $this->initTemplatesDb();
  56. $this->loadCustomTemplates();
  57. }
  58. /**
  59. * Inits system messages helper
  60. *
  61. * @return void
  62. */
  63. protected function initMessages() {
  64. $this->messages = new UbillingMessageHelper();
  65. }
  66. /**
  67. * Initializes the Onvif object.
  68. *
  69. * This method creates a new instance of the Ponvif class and assigns it to the $onvif property.
  70. * The Ponvif class is responsible for handling Onvif-related operations.
  71. *
  72. * @return void
  73. */
  74. protected function initOnvif() {
  75. $this->onvif = new Ponvif();
  76. }
  77. /**
  78. * Inits templates database abstraction layer
  79. *
  80. * @return void
  81. */
  82. protected function initTemplatesDb() {
  83. $this->custTemplatesDb = new NyanORM(self::TABLE_TEMPLATES);
  84. }
  85. /**
  86. * Loads all existing custom templates data from database
  87. *
  88. * @return void
  89. */
  90. protected function loadCustomTemplates() {
  91. $this->allCustTemplatesData = $this->custTemplatesDb->getAll('id');
  92. }
  93. /**
  94. * Polls the ONVIF capable device for information and retrieves the available sources and their stream URLs.
  95. *
  96. * @param string $ip The IP address of the camera.
  97. * @param string $login The login username for accessing the camera.
  98. * @param string $password The login password for accessing the camera.
  99. *
  100. * @return array
  101. */
  102. public function pollDevice($ip, $login, $password) {
  103. $result = array();
  104. if (zb_PingICMP($ip)) {
  105. $this->initOnvif();
  106. $this->onvif->setIPAddress($ip);
  107. $this->onvif->setUsername($login);
  108. $this->onvif->setPassword($password);
  109. try {
  110. $this->onvif->initialize();
  111. $sources = $this->onvif->getSources();
  112. if (!empty($sources)) {
  113. $result['sources'] = $sources[0];
  114. foreach ($sources[0] as $io => $each) {
  115. if (isset($each['profiletoken'])) {
  116. $streamUri = $this->onvif->media_GetStreamUri($each['profiletoken']);
  117. $result['sources'][$io]['url'] = $streamUri;
  118. if (!empty($streamUri)) {
  119. $parsedUrl = parse_url($streamUri);
  120. if (!empty($parsedUrl)) {
  121. $result['sources'][$io]['urldata'] = $parsedUrl;
  122. if (!isset($result['sources'][$io]['urldata']['query'])) {
  123. $result['sources'][$io]['urldata']['query'] = '';
  124. }
  125. }
  126. }
  127. } else {
  128. unset($result['sources'][$io]);
  129. }
  130. }
  131. }
  132. } catch (Exception $e) {
  133. //nothing here right now
  134. }
  135. }
  136. return ($result);
  137. }
  138. /**
  139. * Renders the ONVIF device explore form.
  140. *
  141. * This method generates the HTML code for an explore form, which includes input fields for IP, login, password, and a submit button.
  142. *
  143. * @return string
  144. */
  145. public function renderExploreForm() {
  146. $result = '';
  147. $inputs = wf_HiddenInput(self::PROUTE_EXPLORE, 'true');
  148. $inputs .= wf_TextInput(self::PROUTE_EXPLORE_IP, __('IP'), ubRouting::post(self::PROUTE_EXPLORE_IP), false, 16, 'ip') . ' ';
  149. $inputs .= wf_TextInput(self::PROUTE_EXPLORE_LOGIN, __('Login'), ubRouting::post(self::PROUTE_EXPLORE_LOGIN), false, 12, '') . ' ';
  150. $inputs .= wf_PasswordInput(self::PROUTE_EXPLORE_PASSWORD, __('Password'), ubRouting::post(self::PROUTE_EXPLORE_PASSWORD), false, 12, true) . ' ';
  151. $inputs .= wf_Submit(__('Explore'));
  152. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  153. return ($result);
  154. }
  155. public function renderTemplateCreationForm($deviceData) {
  156. $result = '';
  157. $availableSources = array();
  158. $port = 554;
  159. $proto = 'rtsp';
  160. $sound = 0;
  161. $ptz = 0;
  162. if (!empty($deviceData)) {
  163. if (isset($deviceData['sources'])) {
  164. $preselMain='';
  165. $preselSub='';
  166. foreach ($deviceData['sources'] as $io => $each) {
  167. if (isset($each['profilename']) and isset($each['urldata'])) {
  168. $profileLabel = $each['profilename'];
  169. $shortUrl = $each['urldata']['path'];
  170. if (!empty($each['urldata']['query'])) {
  171. $shortUrl .= '?' . $each['urldata']['query'];
  172. }
  173. $port = $each['urldata']['port'];
  174. $proto = $each['urldata']['scheme'];
  175. if (empty($availableSources)) {
  176. $preselMain=$shortUrl; //first of available sources
  177. } else {
  178. $preselSub=$shortUrl; //last source
  179. }
  180. $availableSources[$shortUrl] = $profileLabel . ' (' . $each['width'] . 'x' . $each['height'] . ')';
  181. }
  182. }
  183. //some form here
  184. $inputs = wf_TextInput(self::PROUTE_TPLCREATE_DEV, __('Template name'), '', true, 20, '');
  185. $inputs .= wf_Selector(self::PROUTE_TPLCREATE_MAIN, $availableSources, __('Mainstream'), $preselMain, true);
  186. $inputs .= wf_Selector(self::PROUTE_TPLCREATE_SUB, $availableSources, __('Substream'), $preselSub, true);
  187. $inputs .= wf_CheckInput(self::PROUTE_TPLCREATE_SOUND, __('Sound'), true, false);
  188. $inputs .= wf_CheckInput(self::PROUTE_TPLCREATE_PTZ, __('PTZ'), true, false);
  189. $inputs .= wf_HiddenInput(self::PROUTE_TPLCREATE_PORT, $port);
  190. $inputs .= wf_HiddenInput(self::PROUTE_TPLCREATE_PROTO, $proto);
  191. $inputs .= wf_Submit(__('Create'));
  192. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  193. } else {
  194. $result .= $this->messages->getStyledMessage(__('Device media sources is empty'), 'error');
  195. }
  196. } else {
  197. $result .= $this->messages->getStyledMessage(__('Device polling data is empty'), 'error');
  198. }
  199. return ($result);
  200. }
  201. /**
  202. * Renders polled device data with few controls
  203. *
  204. * @param array $deviceData
  205. *
  206. * @return string
  207. */
  208. public function renderPollingResults($deviceData) {
  209. $result = '';
  210. if (!empty($deviceData)) {
  211. if (isset($deviceData['sources'])) {
  212. $cells = wf_TableCell(__('Profile name'));
  213. $cells .= wf_TableCell(__('Token'));
  214. $cells .= wf_TableCell(__('Encoding'));
  215. $cells .= wf_TableCell(__('Resolution'));
  216. $cells .= wf_TableCell(__('MP'));
  217. $cells .= wf_TableCell(__('FPS'));
  218. $cells .= wf_TableCell(__('Bitrate'));
  219. $cells .= wf_TableCell(__('Protocol'));
  220. $cells .= wf_TableCell(__('Port'));
  221. $cells .= wf_TableCell(__('URL'));
  222. $rows = wf_TableRow($cells, 'row1');
  223. foreach ($deviceData['sources'] as $io => $each) {
  224. $cells = wf_TableCell($each['profilename']);
  225. $cells .= wf_TableCell($each['profiletoken']);
  226. $cells .= wf_TableCell($each['encoding']);
  227. $pix = $each['width'] * $each['height'];
  228. $mpix = round($pix / 1000000, 1);
  229. $cells .= wf_TableCell($each['width'] . 'x' . $each['height'], '', '', 'sorttable_customkey="' . $pix . '"');
  230. $cells .= wf_TableCell($mpix);
  231. $cells .= wf_TableCell($each['fps']);
  232. $cells .= wf_TableCell($each['bitrate'] . ' ' . __('Kbit/s'));
  233. $cells .= wf_TableCell($each['urldata']['scheme']);
  234. $cells .= wf_TableCell($each['urldata']['port']);
  235. $shortUrl = $each['urldata']['path'];
  236. if (!empty($each['urldata']['query'])) {
  237. $shortUrl .= '?' . $each['urldata']['query'];
  238. }
  239. $cells .= wf_TableCell($shortUrl);
  240. $rows .= wf_TableRow($cells, 'row5');
  241. }
  242. $result .= wf_TableBody($rows, '100%', 0, 'sortable resp-table');
  243. $result .= wf_delimiter();
  244. $rawData = wf_tag('pre') . print_r($deviceData, true) . wf_tag('pre', true);
  245. $result .= wf_modal(wf_img('skins/brain.png') . ' ' . __('Device inside'), __('Device inside'), $rawData, 'ubButton', '900', '600');
  246. $result .= wf_modalAuto(web_icon_create() . ' ' . __('Create device model template'), __('Create device model template'), $this->renderTemplateCreationForm($deviceData), 'ubButton');
  247. } else {
  248. $result .= $this->messages->getStyledMessage(__('Device media sources is empty'), 'error');
  249. }
  250. } else {
  251. $result .= $this->messages->getStyledMessage(__('Device polling data is empty'), 'error');
  252. }
  253. return ($result);
  254. }
  255. /**
  256. * Creates a custom device template database record with the given parameters.
  257. *
  258. * @param string $name The name of the custom template.
  259. * @param string $proto The protocol of the custom template.
  260. * @param string $main The main parameter of the custom template.
  261. * @param string $sub The sub parameter of the custom template.
  262. * @param int $rtspport The RTSP port of the custom template.
  263. * @param int $sound The sound parameter of the custom template. Default is 0.
  264. * @param int $ptz The PTZ parameter of the custom template. Default is 0.
  265. *
  266. * @return void
  267. */
  268. public function createCustomTemplate($name, $proto, $main, $sub, $rtspport, $sound = 0, $ptz = 0) {
  269. $nameF = ubRouting::filters($name, 'mres');
  270. $protoF = ubRouting::filters($proto, 'mres');
  271. $mainF = ubRouting::filters($main, 'mres');
  272. $subF = ubRouting::filters($sub, 'mres');
  273. $rtspport = ubRouting::filters($rtspport, 'int');
  274. $sound = ($sound) ? 1 : 0;
  275. $ptz = ($ptz) ? 1 : 0;
  276. $this->custTemplatesDb->data('name', $nameF);
  277. $this->custTemplatesDb->data('proto', $protoF);
  278. $this->custTemplatesDb->data('main', $mainF);
  279. $this->custTemplatesDb->data('sub', $subF);
  280. $this->custTemplatesDb->data('rtspport', $rtspport);
  281. $this->custTemplatesDb->data('sound', $sound);
  282. $this->custTemplatesDb->data('ptz', $ptz);
  283. $this->custTemplatesDb->create();
  284. $newId = $this->custTemplatesDb->getLastId();
  285. log_register('MODELCRAFT CREATE [' . $newId . '] NAME `' . $name . '`');
  286. //reload data from database
  287. $this->loadCustomTemplates();
  288. //generate new template
  289. $this->generateCustomTemplateFile($newId);
  290. }
  291. /**
  292. * Renders the module controls
  293. *
  294. * @return string
  295. */
  296. public function renderControls() {
  297. $result = '';
  298. if (!ubRouting::checkGet(self::ROUTE_EXPLORER)) {
  299. $result .= wf_BackLink(Models::URL_ME);
  300. $result .= wf_Link(self::URL_ME . '&' . self::ROUTE_EXPLORER . '=true', web_icon_search() . ' ' . __('ONVIF device explorer'), false, 'ubButton');
  301. } else {
  302. $result .= wf_BackLink(self::URL_ME);
  303. }
  304. return ($result);
  305. }
  306. /**
  307. * Deletes a custom template by its ID.
  308. *
  309. * @param int $templateId The ID of the template to be deleted.
  310. *
  311. * @return void|string
  312. */
  313. public function deleteCustomTemplate($templateId) {
  314. $result = '';
  315. $templateId = ubRouting::filters($templateId, 'int');
  316. if (isset($this->allCustTemplatesData[$templateId])) {
  317. $templateName = $templateId . self::CUSTOM_TEMPLATE_MARK;
  318. $templateLabel = $this->allCustTemplatesData[$templateId]['name'];
  319. $models = new Models();
  320. if (!$models->isTemplateProtected($templateName)) {
  321. $this->custTemplatesDb->where('id', '=', $templateId);
  322. $this->custTemplatesDb->delete();
  323. $this->destroyCustomTemplateFile($templateName);
  324. log_register('MODELCRAFT DELETE [' . $templateId . '] NAME `' . $templateLabel . '`');
  325. } else {
  326. $result .= __('Something went wrong') . ': ' . __('Template now is used');
  327. }
  328. } else {
  329. $result .= __('Something went wrong') . ': ' . __('Template') . ' [' . $templateId . '] ' . __('not exists');
  330. }
  331. return ($result);
  332. }
  333. /**
  334. * Renders custom device templates list
  335. *
  336. * @return void
  337. */
  338. public function renderCustomTemplatesList() {
  339. $result = '';
  340. if (!empty($this->allCustTemplatesData)) {
  341. $cells = wf_TableCell(__('ID'));
  342. $cells .= wf_TableCell(__('Name'));
  343. $cells .= wf_TableCell(__('Actions'));
  344. $rows = wf_TableRow($cells, 'row1');
  345. foreach ($this->allCustTemplatesData as $io => $each) {
  346. $cells = wf_TableCell($each['id']);
  347. $cells .= wf_TableCell($each['name']);
  348. $delUrl = self::URL_ME . '&' . self::ROUTE_TPL_DEL . '=' . $each['id'];
  349. $actLinks = wf_JSAlert($delUrl, web_delete_icon(), $this->messages->getDeleteAlert());
  350. $cells .= wf_TableCell($actLinks);
  351. $rows .= wf_TableRow($cells, 'row5');
  352. }
  353. $result .= wf_TableBody($rows, '100%', 0, 'sortable resp-table');
  354. //sync templates on render
  355. $this->syncCustomTemplates();
  356. } else {
  357. $result .= $this->messages->getStyledMessage(__('Nothing to show'), 'warning');
  358. }
  359. return ($result);
  360. }
  361. /**
  362. * Syncs the custom templates on FS with existing in database
  363. *
  364. * @return void
  365. */
  366. public function syncCustomTemplates() {
  367. $customTemplates = array();
  368. $customTemplatesRaw = rcms_scandir(Models::CUSTOM_TEMPLATES_PATH);
  369. if (!empty($customTemplatesRaw)) {
  370. foreach ($customTemplatesRaw as $io => $eachTemplate) {
  371. $templateData = rcms_parse_ini_file(Models::CUSTOM_TEMPLATES_PATH . $eachTemplate);
  372. if (is_array($templateData)) {
  373. $customTemplates[$eachTemplate] = $templateData;
  374. }
  375. }
  376. }
  377. //may be some templates is missing?
  378. if (!empty($this->allCustTemplatesData)) {
  379. foreach ($this->allCustTemplatesData as $io => $each) {
  380. $templateName = $each['id'] . self::CUSTOM_TEMPLATE_MARK;
  381. //sync is required
  382. if (!isset($customTemplates[$templateName])) {
  383. $this->generateCustomTemplateFile($each['id']);
  384. log_register('MODELCRAFT SYNC [' . $each['id'] . '] TEMPLATE `' . $templateName . '`');
  385. }
  386. }
  387. }
  388. }
  389. /**
  390. * Deletes a custom template from filesystem
  391. *
  392. * @param string $templateName The name of the template file to be deleted.
  393. * @return void
  394. */
  395. protected function destroyCustomTemplateFile($templateName) {
  396. if (!empty($templateName)) {
  397. $fsPath = Models::CUSTOM_TEMPLATES_PATH . $templateName;
  398. if (file_exists($fsPath)) {
  399. unlink($fsPath);
  400. log_register('MODELCRAFT DESTROY TEMPLATE `' . $templateName . '`');
  401. }
  402. }
  403. }
  404. /**
  405. * Generates a custom device template based on the provided template ID.
  406. *
  407. * @param int $templateId The ID of the template.
  408. *
  409. * @return void
  410. */
  411. protected function generateCustomTemplateFile($templateId) {
  412. $templateId = ubRouting::filters($templateId, 'int');
  413. if (isset($this->allCustTemplatesData[$templateId])) {
  414. $templateName = $templateId . self::CUSTOM_TEMPLATE_MARK;
  415. $fsPath = Models::CUSTOM_TEMPLATES_PATH . $templateName;
  416. $devData = $this->allCustTemplatesData[$templateId];
  417. $templateBody = 'DEVICE="' . $devData['name'] . '"' . PHP_EOL;
  418. $templateBody .= 'PROTO="' . $devData['proto'] . '"' . PHP_EOL;
  419. $templateBody .= 'MAIN_STREAM="' . $devData['main'] . '"' . PHP_EOL;
  420. $templateBody .= 'SUB_STREAM="' . $devData['sub'] . '"' . PHP_EOL;
  421. $templateBody .= 'RTSP_PORT=' . $devData['rtspport'] . '' . PHP_EOL;
  422. $templateBody .= 'HTTP_PORT=80' . PHP_EOL;
  423. $templateBody .= 'SOUND=' . $devData['sound'] . '' . PHP_EOL;
  424. $templateBody .= 'PTZ=' . $devData['ptz'] . '' . PHP_EOL;
  425. file_put_contents($fsPath, $templateBody);
  426. chmod($fsPath, 0777);
  427. log_register('MODELCRAFT GENERATE [' . $templateId . '] TEMPLATE `' . $templateName . '`');
  428. }
  429. }
  430. }