PageRenderTime 71ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/backend/kolab/kolab.php

https://github.com/MikeEvans/PHP-Push-2
PHP | 3372 lines | 3271 code | 31 blank | 70 comment | 124 complexity | 8375fbd2f23bda297a441f04a2031d0d MD5 | raw file
Possible License(s): AGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. Kolab Z-Push Backend
  4. Copyright (C) 2009-2010 Free Software Foundation Europe e.V.
  5. The main author of the Kolab Z-Push Backend is Alain Abbas, with
  6. contributions by .......
  7. This program is Free Software; you can redistribute it and/or
  8. modify it under the terms of version two of the GNU General Public
  9. License as published by the Free Software Foundation.
  10. This program is distributed in the hope that it will be useful, but
  11. WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program; if not, write to the Free Software
  16. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. 02110-1301, USA.
  18. The licensor of the Kolab Z-Push Backend is the
  19. Free Software Foundation Europe (FSFE), Fiduciary Program,
  20. Linienstr. 141, 10115 Berlin, Germany, email:ftf@fsfeurope.org.
  21. */
  22. //define('KOLABBACKEND_VERSION', 'SVN developpment 20100307');
  23. define('KOLABBACKEND_VERSION', '0.6');
  24. include_once('diffbackend.php');
  25. // The is an improved version of mimeDecode from PEAR that correctly
  26. // handles charsets and charset conversion
  27. include_once('mimeDecode.php');
  28. include_once('z_RTF.php');
  29. include_once('z_RFC822.php');
  30. include_once('Horde/Kolab/Kolab_Zpush/lib/kolabActivesyncData.php');
  31. require_once 'Horde/Kolab/Format.php';
  32. class BackendKolab extends BackendDiff {
  33. private $_server = "";
  34. private $_username ="";
  35. private $_domain = "";
  36. private $_password = "";
  37. private $_cache;
  38. private $_deviceType;
  39. private $_deviceAgent;
  40. private $hasDefaultEventFolder =false;
  41. private $hasDefaultContactFolder= false;
  42. private $hasDefaultTaskFolder = false;
  43. private $foMode = false;
  44. private $_mbox;
  45. private $_KolabHomeServer;
  46. private $_cn;
  47. private $_email;
  48. private $sentFolder="";
  49. /* Called to logon a user. These are the three authentication strings that you must
  50. * specify in ActiveSync on the PDA. Normally you would do some kind of password
  51. * check here. Alternatively, you could ignore the password here and have Apache
  52. * do authentication via mod_auth_*
  53. */
  54. function Logon($username, $domain, $password) {
  55. $this->_wasteID = false;
  56. $this->_sentID = false;
  57. $this->_username = $username;
  58. $this->_domain = $domain;
  59. $this->_password = $password;
  60. if (!$this->getLdapAccount())
  61. {
  62. return false;
  63. }
  64. $this->_server = "{" . $this->_KolabHomeServer . ":" . KOLAB_IMAP_PORT . "/imap" . KOLAB_IMAP_OPTIONS . "}";
  65. $this->Log("Connecting to ". $this->_server);
  66. if (!function_exists("imap_open"))
  67. {
  68. debugLog("ERROR BackendIMAP : PHP-IMAP module not installed!!!!!");
  69. $this->Log("module PHP imap not installed ") ;
  70. }
  71. // open the IMAP-mailbox
  72. $this->_mbox = @imap_open($this->_server , $username, $password, OP_HALFOPEN);
  73. $this->_mboxFolder = "";
  74. if ($this->_mbox) {
  75. debugLog("KolabBackend Version : " . KOLABBACKEND_VERSION);
  76. debugLog("KolabActiveSyndData Version : " .KOLABACTIVESYNCDATA_VERSION);
  77. $this->Log("KolabBackend Version : " . KOLABBACKEND_VERSION);
  78. $this->Log("KolabActiveSyndData Version : " .KOLABACTIVESYNCDATA_VERSION);
  79. $this->Log("IMAP connection opened sucessfully user : " . $username );
  80. // set serverdelimiter
  81. $this->_serverdelimiter = $this->getServerDelimiter();
  82. return true;
  83. }
  84. else {
  85. $this->Log("IMAP can't connect: " . imap_last_error() . " user : " . $this->_user . " Mobile ID:" . $this->_devid);
  86. return false;
  87. }
  88. }
  89. /* Called before shutting down the request to close the IMAP connection
  90. */
  91. function Logoff() {
  92. if ($this->_mbox) {
  93. // list all errors
  94. $errors = imap_errors();
  95. if (is_array($errors)) {
  96. foreach ($errors as $e) debugLog("IMAP-errors: $e");
  97. }
  98. @imap_close($this->_mbox);
  99. debugLog("IMAP connection closed");
  100. $this->Log("IMAP connection closed");
  101. unset($this->_cache);
  102. }
  103. }
  104. /* Called directly after the logon. This specifies the client's protocol version
  105. * and device id. The device ID can be used for various things, including saving
  106. * per-device state information.
  107. * The $user parameter here is normally equal to the $username parameter from the
  108. * Logon() call. In theory though, you could log on a 'foo', and then sync the emails
  109. * of user 'bar'. The $user here is the username specified in the request URL, while the
  110. * $username in the Logon() call is the username which was sent as a part of the HTTP
  111. * authentication.
  112. */
  113. function Setup($user, $devid, $protocolversion) {
  114. $this->_user = $user;
  115. $this->_devid = $devid;
  116. $this->_protocolversion = $protocolversion;
  117. if ($devid == "")
  118. {
  119. //occurs in the OPTION Command
  120. return true;
  121. }
  122. $this->_deviceType=$_REQUEST["DeviceType"];
  123. $this->_deviceAgent=$_SERVER["HTTP_USER_AGENT"];
  124. $this->Log("Setup : " . $user. " Mobile ID :" . $devid. " Proto Version : " . $protocolversion ." DeviceType : ". $this->_deviceType . " DeviceAgent : ". $this->_deviceAgent);
  125. $this->_cache=new userCache();
  126. $this->CacheCheckVersion();
  127. //read globalparam .
  128. $gp=$this->kolabReadGlobalParam();
  129. $mode=KOLAB_MODE;
  130. if ($gp != false )
  131. {
  132. //search if serial already in it;
  133. if ( $gp->getDeviceType($devid))
  134. {
  135. if ( $gp->getDeviceMode($devid) != -1)
  136. {
  137. $mode=$gp->getDeviceMode($devid);
  138. }
  139. }
  140. else
  141. {
  142. //no present we must write it;
  143. $gp->setDevice($devid,$this->_deviceType) ;
  144. if ( ! $this->kolabWriteGlobalParam($gp))
  145. {
  146. $this->Log("ERR cant write Globalparam");
  147. }
  148. }
  149. }
  150. switch($mode)
  151. {
  152. case 0:$this->foMode = false;
  153. $this->Log("NOTICE : Forced to flatmode") ;
  154. break;
  155. case 1:$this->foMode = true;
  156. $this->Log("NOTICE : Forced to foldermode") ;
  157. break;
  158. case 2:$this->foMode = $this->findMode();
  159. break;
  160. }
  161. return true;
  162. }
  163. /* Sends a message which is passed as rfc822. You basically can do two things
  164. * 1) Send the message to an SMTP server as-is
  165. * 2) Parse the message yourself, and send it some other way
  166. * It is up to you whether you want to put the message in the sent items folder. If you
  167. * want it in 'sent items', then the next sync on the 'sent items' folder should return
  168. * the new message as any other new message in a folder.
  169. */
  170. private function findMode()
  171. {
  172. $type=explode(":",KOLAB_MOBILES_FOLDERMODE);
  173. if (in_array(strtolower($this->_deviceType),$type))
  174. {
  175. $this->Log("NOTICE : findMode Foldermode") ;
  176. return 1;
  177. }
  178. $this->Log("NOTICE : findMode Flatmode") ;
  179. return 0;
  180. }
  181. function SendMail($rfc822, $forward = false, $reply = false, $parent = false) {
  182. debugLog("IMAP-SendMail: " . $rfc822 . "for: $forward reply: $reply parent: $parent" );
  183. //
  184. $mobj = new Mail_mimeDecode($rfc822);
  185. $message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8'));
  186. $toaddr = $ccaddr = $bccaddr = "";
  187. if(isset($message->headers["to"]))
  188. $toaddr = $this->parseAddr(Mail_RFC822::parseAddressList($message->headers["to"]));
  189. if(isset($message->headers["cc"]))
  190. $ccaddr = $this->parseAddr(Mail_RFC822::parseAddressList($message->headers["cc"]));
  191. if(isset($message->headers["bcc"]))
  192. $bccaddr = $this->parseAddr(Mail_RFC822::parseAddressList($message->headers["bcc"]));
  193. // save some headers when forwarding mails (content type & transfer-encoding)
  194. $headers = "";
  195. $forward_h_ct = "";
  196. $forward_h_cte = "";
  197. $use_orgbody = false;
  198. // clean up the transmitted headers
  199. // remove default headers because we are using imap_mail
  200. $changedfrom = false;
  201. $returnPathSet = false;
  202. $body_base64 = false;
  203. $org_charset = "";
  204. foreach($message->headers as $k => $v) {
  205. if ($k == "subject" || $k == "to" || $k == "cc" || $k == "bcc")
  206. continue;
  207. if ($k == "content-type") {
  208. // save the original content-type header for the body part when forwarding
  209. if ($forward) {
  210. $forward_h_ct = $v;
  211. continue;
  212. }
  213. // set charset always to utf-8
  214. $org_charset = $v;
  215. $v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v);
  216. }
  217. if ($k == "content-transfer-encoding") {
  218. // if the content was base64 encoded, encode the body again when sending
  219. if (trim($v) == "base64") $body_base64 = true;
  220. // save the original encoding header for the body part when forwarding
  221. if ($forward) {
  222. $forward_h_cte = $v;
  223. continue;
  224. }
  225. }
  226. // if the message is a multipart message, then we should use the sent body
  227. if (!$forward && $k == "content-type" && preg_match("/multipart/i", $v)) {
  228. $use_orgbody = true;
  229. }
  230. // check if "from"-header is set
  231. if ($k == "from" ) {
  232. $changedfrom = true;
  233. if (! trim($v) )
  234. {
  235. $v = $this->_email;
  236. }
  237. }
  238. // check if "Return-Path"-header is set
  239. if ($k == "return-path") {
  240. $returnPathSet = true;
  241. if (! trim($v) ) {
  242. $v = $this->_email;
  243. }
  244. }
  245. // all other headers stay
  246. if ($headers) $headers .= "\n";
  247. $headers .= ucfirst($k) . ": ". $v;
  248. }
  249. // set "From" header if not set on the device
  250. if( !$changedfrom){
  251. $v = $this->_email;
  252. if ($headers) $headers .= "\n";
  253. $headers .= 'From: '.$v;
  254. }
  255. // set "Return-Path" header if not set on the device
  256. if(!$returnPathSet){
  257. $v = $this->_email;
  258. if ($headers) $headers .= "\n";
  259. $headers .= 'Return-Path: '.$v;
  260. }
  261. // if this is a multipart message with a boundary, we must use the original body
  262. if ($use_orgbody) {
  263. list(,$body) = $mobj->_splitBodyHeader($rfc822);
  264. }
  265. else
  266. $body = $this->getBody($message);
  267. // reply
  268. if (isset($reply) && isset($parent) && $reply && $parent) {
  269. $this->imap_reopenFolder($parent);
  270. // receive entire mail (header + body) to decode body correctly
  271. $origmail = @imap_fetchheader($this->_mbox, $reply, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $reply, FT_PEEK | FT_UID);
  272. $mobj2 = new Mail_mimeDecode($origmail);
  273. // receive only body
  274. $body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')));
  275. // unset mimedecoder & origmail - free memory
  276. unset($mobj2);
  277. unset($origmail);
  278. }
  279. // encode the body to base64 if it was sent originally in base64 by the pda
  280. // the encoded body is included in the forward
  281. if ($body_base64) $body = base64_encode($body);
  282. // forward
  283. if (isset($forward) && isset($parent) && $forward && $parent) {
  284. $this->imap_reopenFolder($parent);
  285. // receive entire mail (header + body)
  286. $origmail = @imap_fetchheader($this->_mbox, $forward, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $forward, FT_PEEK | FT_UID);
  287. // build a new mime message, forward entire old mail as file
  288. list($aheader, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte);
  289. // unset origmail - free memory
  290. unset($origmail);
  291. // add boundary headers
  292. $headers .= "\n" . $aheader;
  293. }
  294. $headers .="\n";
  295. $send = @imap_mail ( $toaddr, $message->headers["subject"], $body, $headers, $ccaddr, $bccaddr);
  296. $errors = imap_errors();
  297. if (is_array($errors)) {
  298. foreach ($errors as $e) debugLog("IMAP-errors: $e");
  299. }
  300. // email sent?
  301. if (!$send) {
  302. debugLog("The email could not be sent. Last-IMAP-error: ". imap_last_error());
  303. }
  304. // add message to the sent folder
  305. // build complete headers
  306. $cheaders = "To: " . $toaddr. "\n";
  307. $cheaders .= $headers;
  308. $asf = false;
  309. //try to see if there are a folder with the annotation
  310. $sent=$this->readDefaultSentItemFolder();
  311. $body=str_replace("\n","\r\n",$body);
  312. $cheaders=str_replace(": ",": ",$cheaders);
  313. $cheaders=str_replace("\n","\r\n",$cheaders);
  314. if ($sent) {
  315. $asf = $this->addSentMessage($sent, $cheaders, $body);
  316. }
  317. else if ($this->sentFolder) {
  318. $asf = $this->addSentMessage($this->sentFolder, $cheaders, $body);
  319. debugLog("IMAP-SendMail: Outgoing mail saved in configured 'Sent' folder '".$this->sentFolder."': ". (($asf)?"success":"failed"));
  320. }
  321. // No Sent folder set, try defaults
  322. else {
  323. debugLog("IMAP-SendMail: No Sent mailbox set");
  324. if($this->addSentMessage("INBOX/Sent", $cheaders, $body)) {
  325. debugLog("IMAP-SendMail: Outgoing mail saved in 'INBOX/Sent'");
  326. $asf = true;
  327. }
  328. else if ($this->addSentMessage("Sent", $cheaders, $body)) {
  329. debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent'");
  330. $asf = true;
  331. }
  332. else if ($this->addSentMessage("Sent Items", $cheaders, $body)) {
  333. debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent Items'");
  334. $asf = true;
  335. }
  336. }
  337. $errors = imap_errors();
  338. if (is_array($errors)) {
  339. foreach ($errors as $e) debugLog("IMAP-errors: $e");
  340. }
  341. // unset mimedecoder - free memory
  342. unset($mobj);
  343. return ($send && $asf);
  344. }
  345. /* Should return a wastebasket folder if there is one. This is used when deleting
  346. * items; if this function returns a valid folder ID, then all deletes are handled
  347. * as moves and are sent to your backend as a move. If it returns FALSE, then deletes
  348. * are always handled as real deletes and will be sent to your importer as a DELETE
  349. */
  350. function GetWasteBasket() {
  351. return $this->_wasteID;
  352. }
  353. private function GetMessagesListByType($foldertype,$cutoffdate)
  354. {
  355. $lastfolder="";
  356. $messages=array();
  357. $list = @imap_getmailboxes($this->_mbox, $this->_server, "*");
  358. if (is_array($list)) {
  359. $list = array_reverse($list);
  360. foreach ($list as $val) {
  361. //$folder=imap_utf7_decode(substr($val->name, strlen($this->_server)));
  362. $folder=substr($val->name, strlen($this->_server));
  363. //$this->saveFolderAnnotation($folder);
  364. $ft=$this->kolabFolderType($folder);
  365. if ($ft != $foldertype)
  366. {
  367. continue;
  368. }
  369. $isUser=false;
  370. $isShared=false;
  371. if (substr($folder,0,4) =="user"){$isUser=true;}
  372. if (substr($folder,0,6) =="shared"){$isShared=true;}
  373. $fa=$this->kolabReadFolderParam($folder);
  374. //here we must push theo object in the cache to
  375. //dont have to read it again at each message ( for the alarms)
  376. $this->CacheWriteFolderParam($folder,$fa);
  377. $fa->setFolder($folder);
  378. if ( ! $fa->isForSync($this->_devid))
  379. {
  380. //not set to sync
  381. continue;
  382. }
  383. //want user namespace ?
  384. /*
  385. if ( !KOLAB_USERFOLDER_DIARY && $foldertype == 2 && $isUser)
  386. {
  387. continue;
  388. }
  389. if ( !KOLAB_USERFOLDER_CONTACT && $foldertype == 1 && $isUser)
  390. {
  391. continue;
  392. }
  393. //want shared namespace ?
  394. if ( !KOLAB_SHAREDFOLDER_DIARY && $foldertype == 2 && $isShared)
  395. {
  396. continue;
  397. }
  398. if ( !KOLAB_SHAREDFOLDER_CONTACT && $foldertype == 1 && $isShared)
  399. {
  400. continue;
  401. }
  402. */
  403. if ( $this->CacheGetDefaultFolder($foldertype) == false)
  404. {
  405. //no default
  406. if (substr($folder,0,5) == "INBOX")
  407. {
  408. $n=array_pop(explode("/",$folder));
  409. $result=false;
  410. switch($foldertype)
  411. {
  412. case 1: $result=$this->isDefaultFolder($n,KOLAB_DEFAULTFOLDER_CONTACT);
  413. break;
  414. case 2: $result=$this->isDefaultFolder($n,KOLAB_DEFAULTFOLDER_DIARY);
  415. break;
  416. case 3: $result=$this->isDefaultFolder($n,KOLAB_DEFAULTFOLDER_TASK);
  417. break;
  418. }
  419. if ( $result == true)
  420. {
  421. $this->forceDefaultFolder($foldertype,$folder);
  422. }
  423. else
  424. {
  425. $lastfolder=$folder;
  426. }
  427. }
  428. }
  429. $this->imap_reopenFolder($folder);
  430. /*trying optimizing the reads*/
  431. /*if ($this->isFolderModified($folder) == false )
  432. {
  433. $this->Log("NOTICE : folder not modified $folder");
  434. $message_folder=$this->CacheReadMessageList($folder);
  435. if (count($message)> 0)
  436. {
  437. $messages=array_merge($messages,$message_folder);
  438. continue;
  439. }
  440. } */
  441. $overviews = @imap_fetch_overview($this->_mbox, "1:*",FT_UID);
  442. if (!$overviews) {
  443. debugLog("IMAP-GetMessageList: $folder Failed to retrieve overview");
  444. } else {
  445. $message_infolder=array();
  446. foreach($overviews as $overview) {
  447. $date = "";
  448. $vars = get_object_vars($overview);
  449. if (array_key_exists( "deleted", $vars) && $overview->deleted)
  450. continue;
  451. $message=$this->KolabStat($folder,$overview);
  452. if (! $message){continue;}
  453. //cutoffdate for appointment
  454. if ( $foldertype == 2)
  455. {
  456. //look for kolabuid
  457. $this->Log("try cutoffdate for message id ".$message["id"]);
  458. $enddate= $this->CacheReadEndDate($folder,$message["id"]);
  459. if ($enddate != - 1 && $cutoffdate > $enddate)
  460. {
  461. //cuteoffdate
  462. $this->Log("cuteoffDate :" . $message["id"] );
  463. continue;
  464. }
  465. if ( substr($folder,0,5) != "INBOX")
  466. {
  467. if ($this->CacheReadSensitivity($message["id"]))
  468. {
  469. //check if private for namespace <> INBOX
  470. continue;
  471. }
  472. }
  473. }
  474. //check if key is duplicated
  475. if (isset($checkId[$message["id"]]))
  476. {
  477. //uid exist
  478. $this->Log("Key : " .$message["id"] ." duplicated folder :" . $folder ." Imap id : " . $checkId[$message["id"]]);
  479. debugLog("Key : " .$message["id"] ." duplicated folder :" . $folder ." Imap id : " . $checkId[$message["id"]]);
  480. //rewrite the index to have the good imapid
  481. $id=array_pop(explode("/",$checkId[$message["id"]]));
  482. $this->CacheCreateIndex($folder,$message["id"],$id);
  483. continue;
  484. }
  485. else
  486. {
  487. $checkId[$message["id"]] = $message["mod"];
  488. }
  489. //here check the cutdate for appointments
  490. debugLog("ListMessage : " . $message["id"] . "->" . $message["mod"] ) ;
  491. $messages[]=$message;
  492. $message_infolder[]=$message;
  493. }
  494. $this->CacheStoreMessageList($folder,$message_infolder);
  495. }
  496. }
  497. //check if we found a default folder for this type
  498. if ( $this->CacheGetDefaultFolder($foldertype) == false)
  499. {
  500. //no we pur the last folder found as default;
  501. $this->forceDefaultFolder($foldertype,$lastfolder);
  502. }
  503. unset($checkId);
  504. unset($overviews);
  505. return $messages;
  506. }
  507. }
  508. private function statImapFolder($folder)
  509. {
  510. $info=imap_status($this->_mbox, $this->_server .$folder, SA_ALL) ;
  511. return serialize($info);
  512. }
  513. /* Should return a list (array) of messages, each entry being an associative array
  514. * with the same entries as StatMessage(). This function should return stable information; ie
  515. * if nothing has changed, the items in the array must be exactly the same. The order of
  516. * the items within the array is not important though.
  517. *
  518. * The cutoffdate is a date in the past, representing the date since which items should be shown.
  519. * This cutoffdate is determined by the user's setting of getting 'Last 3 days' of e-mail, etc. If
  520. * you ignore the cutoffdate, the user will not be able to select their own cutoffdate, but all
  521. * will work OK apart from that.
  522. */
  523. function GetMessageList($folderid, $cutoffdate)
  524. {
  525. $messages = array();
  526. $checkId = array();
  527. if ($folderid == "VIRTUAL/calendar")
  528. {
  529. //flat mode
  530. //search all folders of type calendar
  531. $messages=$this->GetMessagesListByType(2,$cutoffdate);
  532. }
  533. else if ($folderid == "VIRTUAL/contacts")
  534. {
  535. $messages=$this->GetMessagesListByType(1,$cutoffdate);
  536. }
  537. else if ($folderid == "VIRTUAL/tasks")
  538. {
  539. $messages=$this->GetMessagesListByType(3,$cutoffdate);
  540. }
  541. else
  542. {
  543. $this->imap_reopenFolder($folderid, true);
  544. //check if the folder as moved by imap stat
  545. /*
  546. if ($this->isFolderModified($folderid) == false )
  547. {
  548. $this->Log("NOTICE : folder not modified $folderid");
  549. $messages=$this->CacheReadMessageList($folderid);
  550. return $messages;
  551. } */
  552. $overviews = @imap_fetch_overview($this->_mbox, "1:*",FT_UID);
  553. if (!$overviews) {
  554. debugLog("IMAP-GetMessageList: $folderid Failed to retrieve overview");
  555. } else {
  556. foreach($overviews as $overview) {
  557. $date = "";
  558. $vars = get_object_vars($overview);
  559. // cut of deleted messages
  560. if (array_key_exists( "deleted", $vars) && $overview->deleted)
  561. continue;
  562. $folderType=$this->kolabFolderType($folderid);
  563. if ( $folderType> 0)
  564. {
  565. //kolab contacts and appointment special index
  566. //mode is the imap uid because kolab delete the message and recreate a newone in case
  567. //of modification
  568. $message=$this->KolabStat($folderid,$overview);
  569. if (! $message){continue;}
  570. //cutoffdate for appointment
  571. if ( $folderType == 2)
  572. {
  573. //look for kolabuid
  574. $this->Log("try cutoffdate for message id ".$message["id"]);
  575. $enddate= $this->CacheReadEndDate($folderid,$message["id"]);
  576. if ($enddate != - 1 && $cutoffdate > $enddate)
  577. {
  578. //cuteoffdate
  579. $this->Log("cuteoffDate too old");
  580. continue;
  581. }
  582. if ( substr($folderid,0,5) != "INBOX")
  583. {
  584. if ($this->CacheReadSensitivity($message["id"]))
  585. {
  586. //check if private for namespace <> INBOX
  587. continue;
  588. }
  589. }
  590. }
  591. //check if key is duplicated
  592. if (isset($checkId[$message["id"]]))
  593. {
  594. $this->Log("Key : " .$message["id"] ." duplicated folder :" . $folder ." Imap id : " . $checkId[$message["id"]]);
  595. debugLog("Key : " .$message["id"] ." duplicated folder :" . $folder ." Imap id : " . $checkId[$message["id"]]);
  596. //rewrite the index to have the good imapid
  597. $id=array_pop(explode("/",$checkId[$message["id"]]));
  598. $this->CacheCreateIndex($folder,$message["id"],$id);
  599. continue;
  600. }
  601. else
  602. {
  603. $checkId[$message["id"]] = $message["mod"];
  604. }
  605. //here check the cutdate for appointments
  606. debugLog("ListMessage : " . $message["id"] . "->" . $message["mod"] ) ;
  607. $messages[]=$message;
  608. }
  609. else
  610. {
  611. if (array_key_exists( "date", $vars)) {
  612. // message is out of range for cutoffdate, ignore it
  613. if(strtotime($overview->date) < $cutoffdate) continue;
  614. $date = $overview->date;
  615. }
  616. if (array_key_exists( "uid", $vars))
  617. {
  618. $message = array();
  619. $message["mod"] = $date;
  620. $message["id"] = $overview->uid;
  621. // 'seen' aka 'read' is the only flag we want to know about
  622. $message["flags"] = 0;
  623. if(array_key_exists( "seen", $vars) && $overview->seen)
  624. $message["flags"] = 1;
  625. array_push($messages, $message);
  626. }
  627. }
  628. }
  629. }
  630. //clean the index before leave
  631. $this->CacheIndexClean($messages) ;
  632. //$this->Log("Get Message List : " . count($messages)) ;
  633. }
  634. debugLog("MEM GetmessageList End:" . memory_get_usage()) ;
  635. $this->CacheStoreMessageList($folderid,$messages);
  636. return $messages;
  637. }
  638. private function isFolderModified($folder)
  639. {
  640. $newstatus=@imap_status($this->_mbox,$this->_server. $folder,SA_ALL);
  641. $oldstatus=$this->CacheReadImapStatus($folder);
  642. //found the old status;
  643. //we compare
  644. if ( $oldstatus->uidnext == $newstatus->uidnext && $oldstatus->messages == $newstatus->messages)
  645. {
  646. //the folder has not been modified
  647. return False;
  648. }
  649. $this->CacheStoreImapStatus($folder,$newstatus);
  650. return true;
  651. }
  652. /* This function is analogous to GetMessageList.
  653. *
  654. */
  655. function GetFolderList()
  656. {
  657. if ( $this->foMode == true)
  658. {
  659. return $this->GetFolderListFoMode();
  660. }
  661. else
  662. {
  663. return $this->GetFolderListFlMode();
  664. }
  665. }
  666. private function GetFolderListFlMode()
  667. {
  668. $folders = array();
  669. $list = @imap_getmailboxes($this->_mbox, $this->_server, "*");
  670. //add the virtual folders for contacts calendars and tasks
  671. $virtual=array("VIRTUAL/calendar","VIRTUAL/contacts","VIRTUAL/tasks");
  672. //$virtual=array("VIRTUAL/calendar","VIRTUAL/contacts");
  673. foreach ($virtual as $v)
  674. {
  675. $box=array();
  676. $box["id"]=$v;
  677. $box["mod"] =$v;
  678. $box["flags"]=0;
  679. $folders[]=$box;
  680. }
  681. if (is_array($list)) {
  682. $list = array_reverse($list);
  683. foreach ($list as $val) {
  684. $box = array();
  685. // cut off serverstring
  686. $box["flags"]=0;
  687. //$box["id"] = imap_utf7_decode(substr($val->name, strlen($this->_server)));
  688. $box["id"] =substr($val->name, strlen($this->_server));
  689. //rerid the annotations
  690. $this->saveFolderAnnotation($box["id"]);
  691. $foldertype=$this->readFolderAnnotation($box["id"]);
  692. //if folder type > 0 escape
  693. if ( substr($foldertype,0,5) == "event")
  694. {
  695. continue;
  696. }
  697. if ( substr($foldertype,0,7) == "contact")
  698. {
  699. continue;
  700. }
  701. if ( substr($foldertype,0,4) == "task")
  702. {
  703. continue;
  704. }
  705. //other folders (mails)
  706. //$box["id"] = imap_utf7_encode( $box["id"]);
  707. $fhir = explode("/", $box["id"]);
  708. if (count($fhir) > 1) {
  709. $box["mod"] = imap_utf7_encode(array_pop($fhir)); // mod is last part of path
  710. $box["parent"] = imap_utf7_encode(implode("/", $fhir)); // parent is all previous parts of path
  711. }
  712. else {
  713. $box["mod"] = imap_utf7_encode($box["id"]);
  714. $box["parent"] = "0";
  715. }
  716. $folders[]=$box;
  717. }
  718. }
  719. else {
  720. debugLog("GetFolderList: imap_list failed: " . imap_last_error());
  721. }
  722. return $folders;
  723. }
  724. private function GetFolderListFoMode() {
  725. $folders = array();
  726. $list = @imap_getmailboxes($this->_mbox, $this->_server, "*");
  727. $this->hasDefaultEventFolder=false;
  728. $this->hasDefaultContactFolder=false;
  729. $this->hasDefaultTaskFolder=false;
  730. if (is_array($list)) {
  731. //create the
  732. // reverse list to obtain folders in right order
  733. $list = array_reverse($list);
  734. foreach ($list as $val) {
  735. $box = array();
  736. // cut off serverstring
  737. $box["flags"]=0;
  738. //$box["id"] = imap_utf7_decode(substr($val->name, strlen($this->_server)));
  739. $box["id"]= substr($val->name, strlen($this->_server));
  740. //determine the type en default folder
  741. $isUser=false;
  742. $isShared=false;
  743. $isInbox=false;
  744. //rerid the annotations
  745. $this->saveFolderAnnotation($box["id"]);
  746. $foldertype=$this->readFolderAnnotation($box["id"]);
  747. $defaultfolder = false;
  748. //defaultfolder ?
  749. if ( $foldertype == "event.default")
  750. {
  751. $this->hasDefaultEventFolder=true;
  752. $defaultfolder = true;
  753. }
  754. if ( $foldertype == "contact.default")
  755. {
  756. $this->hasDefaultContactFolder=true;
  757. $defaultfolder = true;
  758. }
  759. if ( $foldertype == "task.default")
  760. {
  761. $this->hasDefaultTaskFolder=true;
  762. $defaultfolder = true;
  763. }
  764. // workspace of the folder;
  765. if (substr( $box["id"],0,6) == "shared")
  766. {
  767. //this is a shared folder
  768. $isShared=true;
  769. }
  770. if (substr( $box["id"],0,4) == "user")
  771. {
  772. //this is a User shared folder
  773. $isUser=true;
  774. }
  775. if (substr( $box["id"],0,5) == "INBOX")
  776. {
  777. $isInbox=true;
  778. }
  779. //selection of the folder depending to the setup
  780. if (! $defaultfolder)
  781. {
  782. //test annotation
  783. $fa=$this->kolabReadFolderParam($box["id"]);
  784. //for later use (in getMessage)
  785. $this->CacheWriteFolderParam($box["id"],$fa);
  786. $fa->setfolder($box["id"]);
  787. if ( ! $fa->isForSync($this->_devid))
  788. {
  789. //not set to sync
  790. continue;
  791. }
  792. }
  793. $this->Log("NOTICE SyncFolderList Add folder ".$box["id"]);
  794. //$box["id"] = imap_utf7_encode( $box["id"]);
  795. if ($isShared)
  796. {
  797. $fhir = explode(".", $box["id"]);
  798. $box["mod"] = imap_utf7_encode($fhir[1]);
  799. $box["parent"] = "shared";
  800. }
  801. elseif ($isUser)
  802. {
  803. $box["mod"] = imap_utf7_encode(array_pop($fhir));
  804. $box["parent"] = "user";
  805. }
  806. else
  807. {
  808. // explode hierarchies
  809. $fhir = explode("/", $box["id"]);
  810. $t=count($fhir);
  811. if (count($fhir) > 1) {
  812. $box["mod"] = imap_utf7_encode(array_pop($fhir)); // mod is last part of path
  813. $box["parent"] = imap_utf7_encode(implode("/", $fhir)); // parent is all previous parts of path
  814. }
  815. else {
  816. $box["mod"] = imap_utf7_encode($box["id"]);
  817. $box["parent"] = "0";
  818. }
  819. }
  820. $folders[]=$box;
  821. }
  822. }
  823. else {
  824. debugLog("GetFolderList: imap_list failed: " . imap_last_error());
  825. }
  826. return $folders;
  827. }
  828. /* GetFolder should return an actual SyncFolder object with all the properties set. Folders
  829. * are pretty simple really, having only a type, a name, a parent and a server ID.
  830. */
  831. function GetFolder($id) {
  832. $folder = new SyncFolder();
  833. $folder->serverid = $id;
  834. // explode hierarchy
  835. $fhir = explode("/", $id);
  836. if ( substr($id,0,6) == "shared")
  837. {
  838. $parent="shared";
  839. }
  840. else
  841. {
  842. $ftmp=$fhir;
  843. array_pop($ftmp);
  844. $parent=implode("/", $ftmp);
  845. }
  846. //get annotation type
  847. // compare on lowercase strings
  848. $lid = strtolower($id);
  849. $fimap=$id;
  850. if($lid == "inbox") {
  851. $folder->parentid = "0"; // Root
  852. $folder->displayname = "Inbox";
  853. $folder->type = SYNC_FOLDER_TYPE_INBOX;
  854. }
  855. // courier-imap outputs
  856. else if($lid == "inbox/drafts") {
  857. $folder->parentid = $fhir[0];
  858. $folder->displayname = "Drafts";
  859. $folder->type = SYNC_FOLDER_TYPE_DRAFTS;
  860. }
  861. else if($lid == "inbox/trash") {
  862. $folder->parentid = $fhir[0];
  863. $folder->displayname = "Trash";
  864. $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
  865. $this->_wasteID = $id;
  866. }
  867. else if($lid == "inbox/sent") {
  868. $folder->parentid = $fhir[0];
  869. $folder->displayname = "Sent";
  870. $this->sentFolder=$id;
  871. $folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
  872. $this->_sentID = $id;
  873. }
  874. // define the rest as other-folders
  875. //check if flatmode
  876. else if ( $this->foMode == False && $id == "VIRTUAL/calendar")
  877. {
  878. $folder->parentid ="VIRTUAL";
  879. $folder->displayname = $id;
  880. $folder->type = SYNC_FOLDER_TYPE_APPOINTMENT;
  881. $this->_sentID = $id;
  882. }
  883. else if ( $this->foMode == False && $id == "VIRTUAL/contacts")
  884. {
  885. $folder->parentid = "VIRTUAL";
  886. $folder->displayname = "Contacts";
  887. $folder->type = SYNC_FOLDER_TYPE_CONTACT;
  888. $this->_sentID = $id;
  889. }
  890. else if ( $this->foMode == False && $id == "VIRTUAL/tasks")
  891. {
  892. $folder->parentid = "VIRTUAL";
  893. $folder->displayname = $id;
  894. $folder->type = SYNC_FOLDER_TYPE_TASK;
  895. $this->_sentID = $id;
  896. }
  897. else if ( $this->kolabfolderType($id) == 1)
  898. {
  899. //contact kolab
  900. $folder->parentid = $parent;
  901. $folder->displayname = $this->folderDisplayName($id);
  902. $folder->type = $this->ActiveSyncFolderSyncType($id);
  903. $this->_sentID = $id;
  904. }
  905. else if ($this->kolabfolderType($id) == 2)
  906. {
  907. // shared folder in UPPER ,
  908. $folder->parentid = $parent;
  909. $folder->displayname = $this->folderDisplayName($id);
  910. $folder->type = $this->ActiveSyncFolderSyncType($id);
  911. $this->_sentID = $id;
  912. }
  913. else if ($this->kolabfolderType($id) == 3)
  914. {
  915. $folder->parentid = $parent;
  916. $folder->displayname = $this->folderDisplayName($id);
  917. $folder->type = $this->ActiveSyncFolderSyncType($id);
  918. $this->_sentID = $id;
  919. }
  920. else {
  921. if (count($fhir) > 1) {
  922. $folder->displayname = windows1252_to_utf8(imap_utf7_decode(array_pop($fhir)));
  923. $folder->parentid = implode("/", $fhir);
  924. }
  925. else {
  926. $folder->displayname = windows1252_to_utf8(imap_utf7_decode($id));
  927. $folder->parentid = "0";
  928. }
  929. $folder->type = SYNC_FOLDER_TYPE_OTHER;
  930. }
  931. //advanced debugging
  932. //debugLog("IMAP-GetFolder(id: '$id') -> " . print_r($folder, 1));
  933. return $folder;
  934. }
  935. /* Return folder stats. This means you must return an associative array with the
  936. * following properties:
  937. * "id" => The server ID that will be used to identify the folder. It must be unique, and not too long
  938. * How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
  939. * "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply.
  940. * "mod" => This is the modification signature. It is any arbitrary string which is constant as long as
  941. * the folder has not changed. In practice this means that 'mod' can be equal to the folder name
  942. * as this is the only thing that ever changes in folders. (the type is normally constant)
  943. */
  944. private function folderDisplayName($folder)
  945. {
  946. $f = explode("/", $folder);
  947. if (substr($f[0],0,6) == "shared" )
  948. {
  949. // shared folder in UPPER
  950. $s=explode(".",$folder) ;
  951. return strtoupper(windows1252_to_utf8(imap_utf7_decode($s[1])));
  952. }
  953. if ($f[0] == "INBOX")
  954. {
  955. $type=$this->readFolderAnnotation($folder);
  956. if ($type =="contact.default" || $type =="event.default" || $type =="task.default")
  957. {
  958. //default folder all min lowaercase
  959. $r=windows1252_to_utf8(imap_utf7_decode($f[1]));
  960. return strtolower(windows1252_to_utf8(imap_utf7_decode(array_pop($f))));
  961. }
  962. else
  963. {
  964. //others AA problem when we have sub sub folder
  965. //must keep the last one
  966. return ucfirst(windows1252_to_utf8(imap_utf7_decode(array_pop($f))));
  967. }
  968. }
  969. if ($f[0] == "user")
  970. {
  971. $type=$this->readFolderAnnotation($folder);
  972. $t=explode(".",$type);
  973. //find the user
  974. $fname=array_pop($f);
  975. $r=windows1252_to_utf8(imap_utf7_decode($fname."(".$f[1].")"));
  976. return windows1252_to_utf8($r);
  977. }
  978. }
  979. function StatFolder($id) {
  980. $folder = $this->GetFolder($id);
  981. $stat = array();
  982. $stat["id"] = $id;
  983. $stat["parent"] = $folder->parentid;
  984. $stat["mod"] = $folder->displayname;
  985. return $stat;
  986. }
  987. /* Creates or modifies a folder
  988. * "folderid" => id of the parent folder
  989. * "oldid" => if empty -> new folder created, else folder is to be renamed
  990. * "displayname" => new folder name (to be created, or to be renamed to)
  991. * "type" => folder type, ignored in IMAP
  992. *
  993. */
  994. function ChangeFolder($folderid, $oldid, $displayname, $type){
  995. debugLog("ChangeFolder: (parent: '$folderid' oldid: '$oldid' displayname: '$displayname' type: '$type')");
  996. // go to parent mailbox
  997. $this->imap_reopenFolder($folderid);
  998. // build name for new mailbox
  999. $newname = $this->_server . str_replace(".", $this->_serverdelimiter, $folderid) . $this->_serverdelimiter . $displayname;
  1000. $csts = false;
  1001. // if $id is set => rename mailbox, otherwise create
  1002. if ($oldid) {
  1003. // rename doesn't work properly with IMAP
  1004. // the activesync client doesn't support a 'changing ID'
  1005. //$csts = imap_renamemailbox($this->_mbox, $this->_server . imap_utf7_encode(str_replace(".", $this->_serverdelimiter, $oldid)), $newname);
  1006. }
  1007. else {
  1008. $csts = @imap_createmailbox($this->_mbox, $newname);
  1009. }
  1010. if ($csts) {
  1011. return $this->StatFolder($folderid . "." . $displayname);
  1012. }
  1013. else
  1014. return false;
  1015. }
  1016. /* Should return attachment data for the specified attachment. The passed attachment identifier is
  1017. * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
  1018. * encode any information you need to find the attachment in that 'attname' property.
  1019. */
  1020. function GetAttachmentData($attname) {
  1021. debugLog("getAttachmentDate: (attname: '$attname')");
  1022. list($folderid, $id, $part) = explode(":", $attname);
  1023. $this->imap_reopenFolder($folderid);
  1024. $mail = @imap_fetchheader($this->_mbox, $id, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $id, FT_PEEK | FT_UID);
  1025. $mobj = new Mail_mimeDecode($mail);
  1026. $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $mail, 'crlf' => "\n", 'charset' => 'utf-8'));
  1027. if (isset($message->parts[$part]->body))
  1028. print $message->parts[$part]->body;
  1029. // unset mimedecoder & mail
  1030. unset($mobj);
  1031. unset($mail);
  1032. return true;
  1033. }
  1034. /* StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
  1035. * 'id' …

Large files files are truncated, but you can click here to view the full file