Guest User

Untitled

a guest
Oct 21st, 2024
96
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.19 KB | None | 0 0
  1. <?php
  2.  
  3. class vcard_addressbook_backend extends rcube_addressbook
  4. {
  5. public $primary_key = 'ID';
  6. public $readonly = true;
  7. public $groups = false;
  8. private $name;
  9. private $contacts = [];
  10. private $filter;
  11.  
  12. public function __construct($name, $vcard_file)
  13. {
  14. $this->ready = true;
  15. $this->name = $name;
  16. $this->load_vcard_file($vcard_file);
  17. }
  18.  
  19. /**
  20. * Load the contacts from a .vcard file
  21. */
  22. private function load_vcard_file($vcard_file)
  23. {
  24. // error_log("load_vcard_file: " . $vcard_file); // Log contacts without ID
  25.  
  26. if (file_exists($vcard_file)) {
  27. $vcard_data = file_get_contents($vcard_file);
  28.  
  29. // error_log("VCard data: " . $vcard_data); // Log if file doesn't exist
  30.  
  31. $this->contacts = $this->parse_vcards($vcard_data, pathinfo($vcard_file, PATHINFO_FILENAME));
  32.  
  33. // error_log("Loaded contacts: " . print_r($this->contacts, true)); // Log loaded contacts
  34. } else {
  35. error_log("VCard file does not exist: " . $vcard_file); // Log if file doesn't exist
  36. }
  37. }
  38.  
  39. /**
  40. * Parse VCard data into a list of contacts
  41. */
  42. private function parse_vcards($vcard_data, $filename)
  43. {
  44. $contacts = [];
  45. $vcard_blocks = preg_split('/(?=BEGIN:VCARD)/', $vcard_data);
  46.  
  47. foreach ($vcard_blocks as $vcard) {
  48. // Validate that the vCard block starts with BEGIN:VCARD
  49. if (strpos($vcard, 'BEGIN:VCARD') === false) {
  50. error_log("Skipping invalid block: " . $vcard);
  51. continue;
  52. }
  53.  
  54. $contact = [];
  55. $lines = explode("\n", trim($vcard));
  56.  
  57. foreach ($lines as $line) {
  58. $line = trim($line);
  59. if (empty($line)) {
  60. continue; // Skip empty lines
  61. }
  62.  
  63. // Parse vCard properties with improved structure
  64. if (strpos($line, 'UID:') === 0) {
  65. $contact['ID'] = trim(substr($line, strlen('UID:')));
  66. } elseif (strpos($line, 'FN:') === 0) {
  67. $fullname = trim(substr($line, strlen('FN:')));
  68. $contact['name'] = $fullname;
  69.  
  70. // Construct firstname and surname if they are not already set
  71. $name_parts = explode(' ', $fullname);
  72. $contact['firstname'] = $name_parts[0] ?? '';
  73. $contact['surname'] = end($name_parts) ?? ''; // Get last name
  74. } elseif (strpos($line, 'EMAIL:') === 0) {
  75. $contact['email'] = strtolower(trim(substr($line, strlen('EMAIL:'))));
  76. } elseif (strpos($line, 'TEL:') === 0) {
  77. // Allow multiple phone numbers
  78. $contact['phone'][] = trim(substr($line, strlen('TEL:')));
  79. } elseif (strpos($line, 'ORG:') === 0) {
  80. $contact['org'] = trim(substr($line, strlen('ORG:')));
  81. } elseif (strpos($line, 'TITLE:') === 0) {
  82. $contact['title'] = trim(substr($line, strlen('TITLE:')));
  83. } elseif (strpos($line, 'ADR:') === 0) {
  84. // Allow multiple addresses
  85. $contact['address'][] = trim(substr($line, strlen('ADR:')));
  86. } elseif (strpos($line, 'NOTE:') === 0 && strpos($line, 'GB Archery Number') !== false) {
  87. $contact['gb_archerynumber'] = trim(substr($line, strpos($line, ':') + 1));
  88. }
  89. }
  90.  
  91. // Ensure the contact has an ID before adding
  92. if (!empty($contact['ID'])) {
  93. // Flatten the address and phone into strings if they are arrays
  94. if (isset($contact['phone']) && is_array($contact['phone'])) {
  95. $contact['phone'] = implode(', ', array_map('trim', $contact['phone'])); // Join multiple phone numbers
  96. }
  97. if (isset($contact['address']) && is_array($contact['address'])) {
  98. $contact['address'] = implode('; ', array_map('trim', $contact['address'])); // Join multiple addresses
  99. }
  100.  
  101. // Add groups based on the filename
  102. $contact['groups'] = [basename($filename, '.vcf')]; // Assuming the file is in .vcf format
  103.  
  104. $contacts[] = $contact;
  105. } else {
  106. error_log("Skipping contact with no ID: " . print_r($contact, true));
  107. }
  108. }
  109.  
  110. return $contacts;
  111. }
  112.  
  113. #[Override]
  114. public function set_search_set($filter) {
  115. error_log("Setting search filter: " . print_r($filter, true));
  116. $this->filter = $filter; // Set the search filter
  117. }
  118.  
  119. #[Override]
  120. public function get_search_set() {
  121. return $this->filter; // Return the current search filter
  122. }
  123.  
  124. #[Override]
  125. public function reset() {
  126. $this->contacts = []; // Reset contacts
  127. $this->filter = null; // Reset filter
  128. $this->result = null;
  129. }
  130.  
  131. #[Override]
  132. public function list_records($cols = null, $subset = 0, $nocount = false)
  133. {
  134. error_log("list_records() called.");
  135.  
  136. $result = new rcube_result_set();
  137.  
  138. // Add each contact to the result set
  139. foreach ($this->contacts as $contact) {
  140. // Only add contacts that match the filter
  141. if ($this->is_matching_filter($contact)) {
  142. $result->add($contact);
  143. }
  144. }
  145.  
  146. // Set the count of records in the result set
  147. if (!$nocount) {
  148. $result->count = count($result->records); // Set count of filtered records
  149. }
  150.  
  151. // error_log("list_records() result set: " . print_r($result, true));
  152.  
  153. return $result;
  154. }
  155.  
  156. #[Override]
  157. public function search($fields, $value, $mode = 0, $select = true, $nocount = false, $required = [])
  158. {
  159. error_log("fields: " . $fields);
  160. error_log("value: " . $value);
  161.  
  162. $this->filter[$fields] = $value;
  163.  
  164. error_log("$this->filter: " . $this->filter);
  165.  
  166. // Log input for debugging
  167. error_log("search() called with fields: " . print_r($fields, true) . " and value: " . print_r($value, true));
  168.  
  169. $result = new rcube_result_set();
  170.  
  171. // Convert '*' wildcard to all available fields if necessary
  172. if (is_string($fields) && $fields === '*') {
  173. $fields = array_keys($this->contacts[0]); // Assuming all contacts have the same structure
  174. } elseif (!is_array($fields)) {
  175. error_log("Warning: Expected fields to be an array, received: " . gettype($fields));
  176. return $result; // Exit early if $fields is not an array
  177. }
  178.  
  179. foreach ($this->contacts as $contact) {
  180. // Ensure $contact is an array
  181. if (!is_array($contact)) {
  182. error_log("Warning: Expected contact to be an array, received: " . gettype($contact));
  183. continue; // Skip if $contact is not an array
  184. }
  185.  
  186. // Check for a match across the specified fields
  187. foreach ($fields as $field) {
  188. // Only proceed if the field exists in the contact
  189. if (isset($contact[$field])) {
  190. $data = is_array($contact[$field]) ? implode(' ', $contact[$field]) : (string)$contact[$field];
  191. if (stripos($data, $value) !== false && $this->is_matching_filter($contact)) {
  192. error_log("Matching contact found: " . print_r($contact, true));
  193. $result->add($contact);
  194. break; // Stop searching other fields for this contact if a match is found
  195. }
  196. }
  197. }
  198. }
  199.  
  200. // Set the count of records in the result set based on the number of entries added
  201. if (!$nocount) {
  202. $result->count = count($result->records); // Set count of matched records
  203. }
  204.  
  205. // Log the result for debugging
  206. error_log("search() result set: " . print_r($result, true));
  207.  
  208. return $result;
  209. }
  210.  
  211. #[Override]
  212. public function count()
  213. {
  214. // Count only the contacts that match the current filter
  215. $count = 0;
  216.  
  217. foreach ($this->contacts as $contact) {
  218. if ($this->is_matching_filter($contact)) {
  219. $count++; // Increment count for each matching contact
  220. }
  221. }
  222.  
  223. return $count; // Return the count of matching contacts
  224. }
  225.  
  226. #[Override]
  227. public function get_record($id, $assoc = false)
  228. {
  229. // Create a new rcube_result_set to hold the results
  230. $this->result = new rcube_result_set(0);
  231.  
  232. error_log("get_record id: " . $id);
  233.  
  234. // Search for the contact with the matching ID
  235. foreach ($this->contacts as $contact) {
  236. if ($contact['ID'] == $id) {
  237. if ($assoc) {
  238. error_log("contact: " . print_r($contact, true));
  239. return $contact; // Return associative array if requested
  240. }
  241.  
  242. // Add the found contact to the result set
  243. $this->result->add($contact);
  244. $this->result->count = 1; // Set the count to 1 since we found one record
  245. break; // Exit the loop once the contact is found
  246. }
  247. }
  248.  
  249. error_log("get_record result: " . print_r($this->result, true));
  250.  
  251. return $this->result; // Return the result set
  252. }
  253.  
  254. #[Override]
  255. public function get_result()
  256. {
  257. error_log("get_result");
  258.  
  259. // Create a new rcube_result_set and add all contacts to it
  260. // $result = new rcube_result_set(count($this->contacts)); // Set the initial count
  261.  
  262. // foreach ($this->contacts as $contact) {
  263. // $result->add($contact); // Add each contact to the result set
  264. // }
  265.  
  266. error_log("result: " . print_r($this->result, true));
  267.  
  268. return $this->result; // Return the result set with all contacts
  269. }
  270.  
  271. #[Override]
  272. public function get_name()
  273. {
  274. return $this->name; // Return the name of the address book
  275. }
  276.  
  277. #[Override]
  278. public function get_record_groups($id)
  279. {
  280. return []; // Assuming no groups for this backend
  281. }
  282.  
  283. #[Override]
  284. public function set_group($gid)
  285. {
  286. // No groups implemented, do nothing
  287. }
  288.  
  289. #[Override]
  290. public function create_group($name)
  291. {
  292. return true; // No groups implemented
  293. }
  294.  
  295. #[Override]
  296. public function delete_group($gid)
  297. {
  298. return false; // No groups implemented
  299. }
  300.  
  301. #[Override]
  302. public function rename_group($gid, $newname, &$newid)
  303. {
  304. return $newname; // No groups implemented
  305. }
  306.  
  307. #[Override]
  308. public function add_to_group($group_id, $ids)
  309. {
  310. return 0; // No groups implemented
  311. }
  312.  
  313. #[Override]
  314. public function remove_from_group($group_id, $ids)
  315. {
  316. return 0; // No groups implemented
  317. }
  318.  
  319. private function is_matching_filter($contact) {
  320. // error_log("is_matching_filter: " . print_r($contact, true) . " filter: " . print_r($this->filter));
  321.  
  322. // Check if a contact matches the current filter criteria
  323. if ($this->filter) {
  324. foreach ($this->filter as $field => $value) {
  325. error_log("field: " . $field);
  326. error_log("value: " . $value);
  327.  
  328. // If the field is "*", check all fields in the contact
  329. if ($field === "*") {
  330. // Search through all fields in the contact
  331. $matchFound = false;
  332. foreach ($contact as $contactField => $contactValue) {
  333. // Ensure $contactValue is a string before calling trim()
  334. if (is_string($contactValue) && stripos(trim($contactValue), trim($value)) !== false) {
  335. $matchFound = true;
  336. break; // Break out if a match is found
  337. }
  338. }
  339. if (!$matchFound) {
  340. error_log("is_matching_filter: false for wildcard search with value: $value");
  341. return false; // No matches found across all fields
  342. }
  343. } else {
  344. // Ensure field exists in the contact and value is trimmed
  345. if (isset($contact[$field]) && is_string($contact[$field]) && stripos(trim($contact[$field]), trim($value)) === false) {
  346. error_log("is_matching_filter: false for field: $field with value: $value");
  347. return false; // Contact does not match the filter
  348. }
  349. }
  350. }
  351. }
  352.  
  353. error_log("is_matching_filter: true");
  354. return true; // Contact matches the filter
  355. }
  356. }
  357.  
  358. class vcard_addressbook extends rcube_plugin
  359. {
  360. private $abook_id = 'vcard';
  361.  
  362. #[Override]
  363. public function init()
  364. {
  365. $this->add_hook('addressbooks_list', [$this, 'address_sources']);
  366. $this->add_hook('addressbook_get', [$this, 'get_address_book']);
  367. }
  368.  
  369. public function address_sources($p)
  370. {
  371. $config = rcmail::get_instance()->config;
  372. $vcard_directory = $config->get('vcard_directory');
  373. // error_log("address_sources() called. vCard directory: " . $vcard_directory);
  374.  
  375. if (is_dir($vcard_directory)) {
  376. $vcard_files = glob($vcard_directory . '/*.vcard');
  377. // error_log("vCard files found: " . print_r($vcard_files, true));
  378.  
  379. foreach ($vcard_files as $vcard_file) {
  380. $abook_name = basename($vcard_file, '.vcard');
  381. // error_log("Loading vCard file: " . $vcard_file);
  382. $abook = new vcard_addressbook_backend($abook_name, $vcard_file);
  383.  
  384. $p['sources'][$this->abook_id . '_' . $abook_name] = [
  385. 'id' => $this->abook_id . '_' . $abook_name,
  386. 'name' => $abook_name,
  387. 'readonly' => $abook->readonly,
  388. 'groups' => $abook->groups,
  389. ];
  390. }
  391. } else {
  392. error_log("vCard directory does not exist or is not readable: " . $vcard_directory);
  393. }
  394.  
  395. // error_log("Address book sources: " . print_r($p['sources'], true));
  396. return $p;
  397. }
  398.  
  399. public function get_address_book($p)
  400. {
  401. $config = rcmail::get_instance()->config;
  402. $vcard_directory = $config->get('vcard_directory');
  403.  
  404. if (strpos($p['id'], $this->abook_id . '_') === 0) {
  405. $abook_name = substr($p['id'], strlen($this->abook_id . '_'));
  406. $vcard_file = $vcard_directory . '/' . $abook_name . '.vcard';
  407.  
  408. if (file_exists($vcard_file)) {
  409. $p['instance'] = new vcard_addressbook_backend($abook_name, $vcard_file);
  410. }
  411. }
  412.  
  413. return $p;
  414. }
  415. }
  416.  
Advertisement
Add Comment
Please, Sign In to add comment