api.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <?php
  2. $api = new Api("skanman", "Api de scanner");
  3. //Envoi d'un scan
  4. $api->route('commit',"Validation d'un envois de scan (json)",'POST',function($request,&$response){
  5. global $conf,$myUser,$_;
  6. $_ = json_decode($request['body'],true);
  7. if(empty($conf->get('skanman_api_user'))) throw new Exception("Api user is missing in erp configuration, please call an administator",501);
  8. if(empty($_['category'])) throw new Exception("Category missing",501);
  9. if(!isset($_['stack'])) throw new Exception("Stack is not defined");
  10. require_once(__DIR__.SLASH.'Scanfile.class.php');
  11. require_once(__DIR__.SLASH.'ScanCategory.class.php');
  12. $category = ScanCategory::load(array('slug'=>$_['category']) );
  13. Plugin::need('document/Element,document/ElementRight');
  14. Plugin::need('client/Client');
  15. $myUser = User::byLogin($conf->get('skanman_api_user'));
  16. $_SESSION['currentUser'] = serialize($myUser);
  17. $targetUser = new User();
  18. $targetUser->login = 'anonymous';
  19. if(!empty($_['user']) && !empty($_['user']['login'])) $targetUser = User::byLogin($_['user']['login']);
  20. $owner = $myUser;
  21. if($category->slug=='mine'){
  22. $category->path = $targetUser->fullName();
  23. $owner = $targetUser;
  24. }
  25. //Récuperation du dossier de construction de l'id commité
  26. $constructionDirectory = File::dir().'skanman'.SLASH.'tmp'.SLASH.$_['id'];
  27. if(!file_exists($constructionDirectory)) throw new Exception("Le dossier de construction '".$_['id']."' n'existe pas");
  28. //si la catégorie est dossier perso, on modifie le nom du dossier
  29. if($category->slug=='mine'){
  30. $category->path = $targetUser->fullName();
  31. $owner = $targetUser;
  32. }
  33. //Dossier de catégorie d'envois
  34. $path = Element::root().$category->path.SLASH;
  35. //Si le dossier de catégorie n'existe pas, on le créé
  36. if(!file_exists($path)){
  37. //Si la catégorie est dossier perso, on donne les droits uniquement au user concerné
  38. if($category->slug=='mine'){
  39. Element::addFolder($path,true,false, $owner->login);
  40. //Sinon on donne les droits au rang de tous les utilisateurs skanman
  41. }else{
  42. $element = Element::addFolder($path,true,false);
  43. $right = new ElementRight();
  44. $right->element = $element->id;
  45. $right->entity = 'rank';
  46. $right->uid = $conf->get('skanman_users_rank');
  47. $right->read = true;
  48. $right->edit = true;
  49. $right->recursive = true;
  50. $right->save();
  51. }
  52. }
  53. //Racine du fichier dans la ged
  54. $fileRoot = $path.$_['id'];
  55. //Liste des fichiers temporaires du dossier de consturction
  56. $files = glob($constructionDirectory.SLASH.'*.jpeg');
  57. $filesPathes = array();
  58. switch($_['stack']){
  59. case 'one-file':
  60. //Convertion en pdf
  61. if(!empty($conf->get('skanman_image_to_pdf'))){
  62. $filePath=$fileRoot.'.pdf';
  63. Image::toPdf($files,$filePath);
  64. //Convertion en image concatenée
  65. }else{
  66. $filePath=$fileRoot.'.jpeg';
  67. $response[] = $files;
  68. //print_r($filePath);
  69. Image::concatenate($files,$filePath);
  70. }
  71. $filesPathes[] = $filePath;
  72. break;
  73. case 'multiple-files':
  74. foreach($files as $i=>$file){
  75. //Convertion en pdf
  76. if(!empty($conf->get('skanman_image_to_pdf'))){
  77. $filePath=$fileRoot.'-'.$i.'.pdf';
  78. Image::toPdf(array($file),$filePath);
  79. //déplacement simple
  80. }else{
  81. $filePath=$fileRoot.'-'.$i.'.jpeg';
  82. rename($file,$filePath);
  83. }
  84. $filesPathes[] = $filePath;
  85. }
  86. break;
  87. }
  88. //Envoi par mail si l'option est sélectionnée
  89. if($category->slug == 'mail'){
  90. $mail = new Mail();
  91. $mail->recipients['to'][] = $targetUser->mail;
  92. $mail->title = 'Fichier envoyé depuis le scanner';
  93. $mail->message = $conf->get('skanman_mail_body');
  94. if(empty($mail->message)) $mail->message = 'Bonjour,<br/> le fichier çi-attaché vous a été envoyé depuis le scanner';
  95. foreach($filesPathes as $filePath){
  96. $mail->attachments[] = array(
  97. 'name' => basename($filePath),
  98. 'type' => File::mime($filePath),
  99. 'stream'=>file_get_contents($filePath)
  100. );
  101. }
  102. $mail->send();
  103. }
  104. //Pour chaque fichier définitif
  105. foreach($filesPathes as $filePath){
  106. //Sauvegarde de l'élément depuis le chemin du fichier physique
  107. $file = Element::fromPath($filePath);
  108. if($category->slug== 'mine') $file->creator = $targetUser->login;
  109. $file->save();
  110. //Création des metas infos de l'élément représenté par
  111. $scanFile = new Scanfile();
  112. $scanFile->label = $file->label;
  113. $tags = array();
  114. $tags[] = slugify($category->label);
  115. //Mini detection OCR, nécessite l'installation du paquet ocrmypdf et des paquet dictionnaires de langue
  116. if($conf->get('skanman_ocr_enable')){
  117. $command = array('export LC_ALL=C.UTF-8 && export LANG=C.UTF-8 &&','ocrmypdf');
  118. $command[] = '-l fra'; # it supports multiple languages
  119. $command[] = '--rotate-pages'; # it can fix pages that are misrotated
  120. $command[] = '--deskew'; # it can deskew crooked PDFs!
  121. $command[] = '--jobs 4'; # it uses multiple cores by default
  122. $command[] = '"'.$filePath.'"'; # takes PDF input (or images)
  123. $command[] = 'output_pdf'; # it produces PDF/A by default
  124. $command[] = '--sidecar -'; //__DIR__.SLASH.'output_searchable.pdf'; # produces validated PDF output
  125. $command = implode(' ',$command);
  126. $stream = mb_strtolower(shell_exec($command));
  127. $regexes = array(
  128. array(
  129. 'regex'=>'/[\s\n\t](prestation)[\s\n\t]/is',
  130. 'tag' => 'prestation'
  131. ),
  132. array(
  133. 'regex'=>'/[\s\n\t](m[eé]moire)[\s\n\t]/is',
  134. 'tag' => 'mémoire'
  135. ),
  136. array(
  137. 'regex'=>'/[\s\n\t](cahier des charges?)[\s\n\t]/is',
  138. 'tag' => 'cdc'
  139. ),
  140. array(
  141. 'regex'=>'/[\s\n\t](factur(es?|ations?))[\s\n\t]/is',
  142. 'tag' => 'facture'
  143. ),
  144. array(
  145. 'regex'=>'/[\s\n\t](statistiques?)[\s\n\t]/is',
  146. 'tag' => 'statistiques'
  147. ),
  148. array(
  149. 'regex'=>'/[\s\n\t](r[ée]gl[eé]e?|acquitt[ée]e?|pay[ée]e?)[\s\n\t]/is',
  150. 'tag' => 'acquitté'
  151. ),
  152. array(
  153. 'regex'=>'/[\s\n\t](frais?)[\s\n\t]/is',
  154. 'tag' => 'frais'
  155. ),
  156. array(
  157. 'regex'=>'/[\s\n\t](documentations?)[\s\n\t]/is',
  158. 'tag' => 'documentation'
  159. ),
  160. array(
  161. 'regex'=>'/[\s\n\t](restaurations?|restaurants?)[\s\n\t]/is',
  162. 'tag' => 'restauration'
  163. ),
  164. //emails
  165. array(
  166. 'regex'=>'/([a-z0-9\.\_\-]+@[a-z0-9]+\.(fr|com|net|org|edu|gouv))/is',
  167. 'tag' => '$1'
  168. ),
  169. //dates
  170. array(
  171. 'regex'=>'/[\s\n\t]([0-9]{2}[\-\/][0-9]{2}[\-\/][0-9]{2,4})[\s\n\t]/is',
  172. 'tag' => '$1'
  173. )
  174. );
  175. //Détection des noms de clients en base
  176. foreach(Client::loadAll() as $client){
  177. $regexes[] = array(
  178. 'regex'=>'|[\s\n\t]('.preg_quote($client->label).')[\s\n\t]|is',
  179. 'tag' => '$1'
  180. );
  181. }
  182. foreach($regexes as $regex){
  183. preg_match_all($regex['regex'],$stream,$out);
  184. if(count($out)==0 || count($out[0])==0) continue;
  185. if(preg_match('/\$([0-9]*)/is',$regex['tag'],$match)){
  186. if(!isset($out[$match[1]][0])) continue;
  187. $regex['tag'] = $out[$match[1]][0];
  188. }
  189. $tags[] = $regex['tag'];
  190. }
  191. }
  192. $scanFile->tag = implode(',',$tags);
  193. $scanFile->creator = $owner->login;
  194. $scanFile->element = $file->id;
  195. $scanFile->size = $file->size;
  196. $scanFile->save();
  197. }
  198. $myUser->getFirms();
  199. $firms = array_values($myUser->firms);
  200. //Save en historique
  201. $history = new History();
  202. $history->scope = 'skanman';
  203. $history->comment = 'Envois d\'un document';
  204. $history->comment .= ' utilisateur cible '.$targetUser->login;
  205. $history->comment .= ' catégorie cible '.$category->label;
  206. $history->save();
  207. //Trigger pour utilisation sur le workflow
  208. if(count($firms)>0 && $firms[0]->has_plugin('fr.core.workflow')){
  209. Plugin::need('workflow/WorkflowEvent');
  210. WorkflowEvent::trigger('skanman-scanfile-send',array('old'=>$scanFile, 'current'=>$scanFile));
  211. }
  212. });
  213. //Envoi d'un scan
  214. $api->route('send',"Envois d'un fichier scanné (binaire) et de ses catégories (json)",'POST',function($request,&$response){
  215. global $conf,$myUser,$_;
  216. $_ = json_decode($request['body'],true);
  217. preg_match('/boundary=([0-9a-z]*)/is',$request['headers']['content-type'],$match);
  218. $boundary = $match[1];
  219. $request['body'] = str_replace("\n\n".'--'.$boundary.'-','',$request['body']);
  220. $parts = explode('--'.$boundary."\n",$request['body']);
  221. $parts = array_filter($parts);
  222. $stream = '';
  223. foreach($parts as $i=>$part){
  224. $part = explode("\n\n",$part,2);
  225. preg_match('/Content-Type\s*:\s*([^;\n]*)/is',$part[0],$match);
  226. $mime = $match[1];
  227. switch($mime){
  228. case 'application/json':
  229. $_ = json_decode($part[1],true);
  230. break;
  231. default:
  232. $part[1] = str_replace('--'.$boundary.'-','',$part[1]);
  233. preg_match('/Content-Disposition\s*:.*filename\s*=\s*"([^;\n]*)"/is',$part[0],$match);
  234. $filename = isset($match[1]) ? $match[1] : 'file.jpeg';
  235. preg_match('/Content-Disposition\s*:.*;\s*name\s*=\s*"([^;\n]*)"/is',$part[0],$match);
  236. $name = isset($match[1]) ? $match[1] : 'file';
  237. $stream = $part[1];
  238. break;
  239. }
  240. }
  241. if(empty($conf->get('skanman_api_user'))) throw new Exception("Api user is missing in erp configuration, please call an administator",501);
  242. if(empty($_['category'])) throw new Exception("Category missing",501);
  243. $workspace = File::dir().'skanman';
  244. if(!file_exists($workspace)) mkdir($workspace,0755,true);
  245. $constructionDirectory = $workspace.SLASH.'tmp'.SLASH.$_['id'];
  246. //On ajoute le @ car le send multiple cause parfois un cas rare : le dossier est créé par un autre process send entre le moment du test de l'existence du dossier
  247. //et sa création effective ce qui cause une erreur
  248. if(!file_exists($constructionDirectory)) @mkdir($constructionDirectory,0755,true);
  249. $filePath = $constructionDirectory.SLASH.$_['number'];
  250. file_put_contents($filePath,$stream);
  251. if(!empty($conf->get('skanman_image_rotate'))) Image::rotate($filePath,$filePath,180);
  252. $response['id'] = $_['id'];
  253. $response['number'] = $_['number'];
  254. $response['success'] = true;
  255. });
  256. //Ping + récuperation des confs erp sur le scanner
  257. $api->route('ping',"Récuperation des configurations erp pour le scanner",'POST',function($request,&$response){
  258. global $conf,$myUser,$_;
  259. $_ = json_decode($request['body'],true);
  260. if(!$myUser || !$myUser->connected()) throw new Exception("Credentials are missing",401);
  261. if(empty($_['synchronizationVersion'])) throw new Exception("synchronization version is missing",401);
  262. if(empty($conf->get('skanman_api_user'))) throw new Exception("Api user is missing in erp configuration, please call an administator",501);
  263. if($conf->get('skanman_synchronization_version')==$_['synchronizationVersion']) return;
  264. require_once(__DIR__.SLASH.'Scanfile.class.php');
  265. require_once(__DIR__.SLASH.'ScanCategory.class.php');
  266. //On force le reload pour eviter le cache
  267. $conf->getAll(true);
  268. $users = array();
  269. $avatars = array();
  270. if(!empty($conf->get('skanman_users_rank'))){
  271. foreach (User::getAll() as $user) {
  272. if(!$user->hasRank($conf->get('skanman_users_rank'))) continue;
  273. $users[] = array(
  274. 'login'=>$user->login,
  275. 'fullName'=>$user->fullName(),
  276. 'mail'=>$user->mail,
  277. 'phone'=>$user->phone,
  278. 'mobile'=>$user->mobile
  279. );
  280. $avatars[$user->login] = array(
  281. 'stream'=>base64_encode(file_get_contents($user->getAvatar(true)) ),
  282. 'extension' => getExt($user->getAvatar(true))
  283. );
  284. }
  285. }
  286. $categories = array();
  287. foreach (ScanCategory::loadAll() as $category)
  288. $categories[$category->slug] = $category->toArray();
  289. $response['configuration'] = array(
  290. 'format' => $conf->get('skanman_scan_format'),
  291. 'color' => $conf->get('skanman_scan_color'),
  292. 'face' => $conf->get('skanman_scan_face'),
  293. 'stack' => $conf->get('skanman_scan_stack'),
  294. 'contrast' => $conf->get('skanman_scan_contrast' ),
  295. 'light' => $conf->get('skanman_scan_light'),
  296. 'resolution' => $conf->get('skanman_scan_resolution' ),
  297. 'categories' => $categories,
  298. 'pageHeight' => $conf->get('skanman_scan_page_height'),
  299. 'synchronizationVersion' => $conf->get('skanman_synchronization_version'),
  300. 'extraCss' => $conf->get('skanman_scan_css'),
  301. 'users' => $users,
  302. 'avatars' => $avatars,
  303. );
  304. $imageDirectory = File::dir().'skanman'.SLASH.'tosend';
  305. if(file_exists($imageDirectory)){
  306. $imagesToSend = glob($imageDirectory.SLASH.'*.{png,jpeg,jpg,bmp}',GLOB_BRACE);
  307. if(count($imagesToSend)>0){
  308. $response['configuration']['images'] = array();
  309. foreach($imagesToSend as $image){
  310. $response['configuration']['images'][] = array(
  311. 'stream'=>base64_encode(file_get_contents($image) ),
  312. 'label' => basename($image)
  313. );
  314. unlink($image);
  315. }
  316. }
  317. }
  318. });
  319. $api->register();