| 1: | <?php |
| 2: | |
| 3: | |
| 4: | |
| 5: | |
| 6: | |
| 7: | |
| 8: | |
| 9: | if (!defined('DEBUG_MODE')) { die(); } |
| 10: | |
| 11: | require APP_PATH.'modules/feeds/hm-feed.php'; |
| 12: | |
| 13: | |
| 14: | |
| 15: | |
| 16: | class Hm_Handler_feed_list_type extends Hm_Handler_Module { |
| 17: | public function process() { |
| 18: | if (array_key_exists('list_path', $this->request->get)) { |
| 19: | $path = $this->request->get['list_path']; |
| 20: | if ($path == 'feeds') { |
| 21: | $this->out('list_path', 'feeds', false); |
| 22: | $this->out('mailbox_list_title', array('All Feeds')); |
| 23: | $this->out('message_list_since', $this->user_config->get('feed_since_setting', DEFAULT_FEED_SINCE)); |
| 24: | $this->out('per_source_limit', $this->user_config->get('feed_limit_setting', DEFAULT_FEED_LIMIT)); |
| 25: | } |
| 26: | elseif (preg_match("/^feeds_.+$/", $path)) { |
| 27: | $this->out('message_list_since', $this->user_config->get('feed_since_setting', DEFAULT_FEED_SINCE)); |
| 28: | $this->out('per_source_limit', $this->user_config->get('feed_limit_setting', DEFAULT_FEED_LIMIT)); |
| 29: | $this->out('list_path', $path, false); |
| 30: | $this->out('custom_list_controls', ' '); |
| 31: | $parts = explode('_', $path, 2); |
| 32: | $details = Hm_Feed_List::dump($parts[1]); |
| 33: | if (!empty($details)) { |
| 34: | $this->out('mailbox_list_title', array('Feeds', $details['name'])); |
| 35: | } |
| 36: | } |
| 37: | } |
| 38: | } |
| 39: | } |
| 40: | |
| 41: | |
| 42: | |
| 43: | |
| 44: | class Hm_Handler_process_feed_limit_setting extends Hm_Handler_Module { |
| 45: | public function process() { |
| 46: | process_site_setting('feed_limit', $this, 'max_source_setting_callback', DEFAULT_FEED_LIMIT); |
| 47: | } |
| 48: | } |
| 49: | |
| 50: | |
| 51: | |
| 52: | |
| 53: | class Hm_Handler_process_feed_since_setting extends Hm_Handler_Module { |
| 54: | public function process() { |
| 55: | process_site_setting('feed_since', $this, 'since_setting_callback', DEFAULT_FEED_SINCE); |
| 56: | } |
| 57: | } |
| 58: | |
| 59: | |
| 60: | |
| 61: | |
| 62: | class Hm_Handler_process_unread_feeds_setting extends Hm_Handler_Module { |
| 63: | public function process() { |
| 64: | function unread_feed_setting_callback($val) { return $val; } |
| 65: | process_site_setting('unread_exclude_feeds', $this, 'unread_feed_setting_callback', DEFAULT_UNREAD_EXCLUDE_FEEDS, true); |
| 66: | } |
| 67: | } |
| 68: | |
| 69: | |
| 70: | |
| 71: | |
| 72: | class Hm_Handler_feed_connect extends Hm_Handler_Module { |
| 73: | public function process() { |
| 74: | $failed = true; |
| 75: | if (isset($this->request->post['feed_connect'])) { |
| 76: | list($success, $form) = $this->process_form(array('feed_id')); |
| 77: | if ($success) { |
| 78: | $feed_data = Hm_Feed_List::dump($form['feed_id']); |
| 79: | if ($feed_data) { |
| 80: | $feed = is_news_feed($feed_data['server']); |
| 81: | if ($feed) { |
| 82: | $failed = false; |
| 83: | Hm_Msgs::add("Successfully connected to the feed", "info"); |
| 84: | } |
| 85: | } |
| 86: | } |
| 87: | if ($failed) { |
| 88: | Hm_Msgs::add("Failed to connect to the feed", "warning"); |
| 89: | } |
| 90: | } |
| 91: | } |
| 92: | } |
| 93: | |
| 94: | |
| 95: | |
| 96: | |
| 97: | class Hm_Handler_delete_feed extends Hm_Handler_Module { |
| 98: | public function process() { |
| 99: | if (isset($this->request->post['delete_feed'])) { |
| 100: | list($success, $form) = $this->process_form(array('feed_id')); |
| 101: | if ($success) { |
| 102: | $res = Hm_Feed_List::del($form['feed_id']); |
| 103: | if ($res) { |
| 104: | $this->out('deleted_server_id', $form['feed_id']); |
| 105: | $this->out('reload_folders', true); |
| 106: | Hm_Msgs::add('Feed deleted'); |
| 107: | $this->session->record_unsaved('Feed deleted'); |
| 108: | } |
| 109: | } |
| 110: | else { |
| 111: | $this->out('old_form', $form); |
| 112: | } |
| 113: | } |
| 114: | } |
| 115: | } |
| 116: | |
| 117: | |
| 118: | |
| 119: | |
| 120: | class Hm_Handler_feed_status extends Hm_Handler_Module { |
| 121: | public function process() { |
| 122: | list($success, $form) = $this->process_form(array('feed_server_ids')); |
| 123: | if ($success) { |
| 124: | $ids = explode(',', $form['feed_server_ids']); |
| 125: | foreach ($ids as $id) { |
| 126: | $start_time = microtime(true); |
| 127: | $feed_data = Hm_Feed_List::dump($id); |
| 128: | if ($feed_data) { |
| 129: | $feed = is_news_feed($feed_data['server']); |
| 130: | if ($feed && $feed->parsed_data) { |
| 131: | $this->out('feed_connect_time', microtime(true) - $start_time); |
| 132: | $this->out('feed_connect_status', 'Connected'); |
| 133: | $this->out('feed_status_server_id', $id); |
| 134: | } |
| 135: | } |
| 136: | } |
| 137: | } |
| 138: | } |
| 139: | } |
| 140: | |
| 141: | |
| 142: | |
| 143: | |
| 144: | class Hm_Handler_feed_message_action extends Hm_Handler_Module { |
| 145: | public function process() { |
| 146: | |
| 147: | list($success, $form) = $this->process_form(array('action_type', 'message_ids')); |
| 148: | if ($success) { |
| 149: | $id_list = explode(',', $form['message_ids']); |
| 150: | foreach ($id_list as $msg_id) { |
| 151: | if (preg_match("/^feeds_(\d)+_.+$/", $msg_id)) { |
| 152: | $parts = explode('_', $msg_id, 3); |
| 153: | $guid = $parts[2]; |
| 154: | switch($form['action_type']) { |
| 155: | case 'unread': |
| 156: | Hm_Feed_Uid_Cache::unread($guid); |
| 157: | break; |
| 158: | case 'read': |
| 159: | Hm_Feed_Uid_Cache::read($guid); |
| 160: | break; |
| 161: | } |
| 162: | } |
| 163: | } |
| 164: | } |
| 165: | } |
| 166: | } |
| 167: | |
| 168: | |
| 169: | |
| 170: | |
| 171: | |
| 172: | class Hm_Handler_feed_list_content extends Hm_Handler_Module { |
| 173: | public function process() { |
| 174: | list($success, $form) = $this->process_form(array('feed_server_ids')); |
| 175: | $dataSources = array_map(fn ($feed) => $feed['id'], Hm_Feed_List::dump()); |
| 176: | if (!$success && empty($dataSources)) { |
| 177: | return; |
| 178: | } |
| 179: | $cache = false; |
| 180: | $terms = $this->request->get['search_terms'] ?? false; |
| 181: | $since = $this->request->get['search_since'] ?? DEFAULT_SEARCH_SINCE; |
| 182: | $fld = $this->request->get['search_fld'] ?? 'TEXT'; |
| 183: | $ids = isset($form['feed_server_ids']) ? explode(',', $form['feed_server_ids']): $dataSources; |
| 184: | $res = array(); |
| 185: | $unread_only = false; |
| 186: | $login_time = $this->session->get('login_time', false); |
| 187: | if ($login_time) { |
| 188: | $this->out('login_time', $login_time); |
| 189: | } |
| 190: | if ($this->get('list_path') == 'unread') { |
| 191: | $limit = $this->user_config->get('unread_per_source_setting', DEFAULT_UNREAD_PER_SOURCE); |
| 192: | $date = process_since_argument($this->user_config->get('unread_since_setting', DEFAULT_UNREAD_SINCE)); |
| 193: | $unread_only = true; |
| 194: | $cutoff_timestamp = strtotime($date); |
| 195: | if ($login_time && $login_time > $cutoff_timestamp) { |
| 196: | $cutoff_timestamp = $login_time; |
| 197: | } |
| 198: | } |
| 199: | elseif ($this->get('list_path') == 'combined_inbox') { |
| 200: | $limit = $this->user_config->get('all_per_source_setting', DEFAULT_ALL_PER_SOURCE); |
| 201: | $date = process_since_argument($this->user_config->get('all_since_setting', DEFAULT_ALL_SINCE)); |
| 202: | $cutoff_timestamp = strtotime($date); |
| 203: | } |
| 204: | else { |
| 205: | $limit = $this->user_config->get('feed_limit_setting', DEFAULT_FEED_LIMIT); |
| 206: | $date = process_since_argument($this->user_config->get('feed_since_setting', DEFAULT_FEED_SINCE)); |
| 207: | $cutoff_timestamp = strtotime($date); |
| 208: | } |
| 209: | foreach($ids as $id) { |
| 210: | $feed_data = Hm_Feed_List::dump($id); |
| 211: | if ($feed_data) { |
| 212: | if (! $terms) { |
| 213: | $cache = feed_memcached_fetch($this, $feed_data); |
| 214: | } |
| 215: | $data = false; |
| 216: | if (is_array($cache) && count($cache) > 0) { |
| 217: | $data = $cache; |
| 218: | } |
| 219: | else { |
| 220: | $feed = is_news_feed($feed_data['server'], $limit); |
| 221: | if ($feed && $feed->parsed_data) { |
| 222: | $data = $feed->parsed_data; |
| 223: | $cache = false; |
| 224: | } |
| 225: | } |
| 226: | if (is_array($data)) { |
| 227: | foreach ($data as $item) { |
| 228: | if (array_key_exists('id', $item) && !array_key_exists('guid', $item)) { |
| 229: | $item['guid'] = $item['id']; |
| 230: | } |
| 231: | elseif (array_key_exists('link', $item) && !array_key_exists('guid', $item)) { |
| 232: | $item['guid'] = $item['link']; |
| 233: | } |
| 234: | if (array_key_exists('link_self', $item) || !array_key_exists('guid', $item)) { |
| 235: | continue; |
| 236: | } |
| 237: | if (!Hm_Feed_Uid_Cache::is_unread(md5($item['guid']))) { |
| 238: | if (isset($item['pubdate']) && strtotime($item['pubdate']) < $cutoff_timestamp) { |
| 239: | continue; |
| 240: | } |
| 241: | elseif (isset($item['dc:date']) && strtotime($item['dc:date']) < $cutoff_timestamp) { |
| 242: | continue; |
| 243: | } |
| 244: | if (isset($item['guid']) && $unread_only && Hm_Feed_Uid_Cache::is_read(md5($item['guid']))) { |
| 245: | continue; |
| 246: | } |
| 247: | } |
| 248: | if ($terms && !search_feed_item($item, $terms, $since, $fld)) { |
| 249: | continue; |
| 250: | } |
| 251: | else { |
| 252: | $item['server_id'] = $id; |
| 253: | $item['server_name'] = $feed_data['name']; |
| 254: | $res[] = $item; |
| 255: | } |
| 256: | } |
| 257: | } |
| 258: | } |
| 259: | } |
| 260: | if (! $cache && ! $terms) { |
| 261: | |
| 262: | feed_memcached_save($this, $feed_data, $res); |
| 263: | } |
| 264: | |
| 265: | usort($res, function($a, $b) { |
| 266: | if (isset($a['pubdate']) && isset($b['pubdate'])) { |
| 267: | return strtotime($b['pubdate']) - strtotime($a['pubdate']); |
| 268: | } |
| 269: | elseif (isset($a['dc:date']) && isset($b['dc:date'])) { |
| 270: | return strtotime($b['dc:date']) - strtotime($a['dc:date']); |
| 271: | } |
| 272: | return 0; |
| 273: | }); |
| 274: | |
| 275: | $this->out('feed_list_data', $res); |
| 276: | if (isset($this->request->get['list_path'])) { |
| 277: | $this->out('feed_list_parent', $this->request->get['list_path']); |
| 278: | } |
| 279: | elseif (isset($this->request->get['page']) && $this->request->get['page'] == 'search') { |
| 280: | $this->out('feed_list_parent', 'search'); |
| 281: | } |
| 282: | $this->out('feed_server_ids', $form['feed_server_ids']); |
| 283: | } |
| 284: | } |
| 285: | |
| 286: | |
| 287: | |
| 288: | |
| 289: | class Hm_Handler_feed_item_content extends Hm_Handler_Module { |
| 290: | public function process() { |
| 291: | $content = ''; |
| 292: | $headers = array(); |
| 293: | list($success, $form) = $this->process_form(array('feed_uid', 'feed_list_path')); |
| 294: | if ($success) { |
| 295: | $path = explode('_', $form['feed_list_path']); |
| 296: | $id = $path[1]; |
| 297: | $feed_items = array(); |
| 298: | $feed_data = Hm_Feed_List::dump($id); |
| 299: | if ($feed_data) { |
| 300: | $cache = feed_memcached_fetch($this, $feed_data); |
| 301: | if (is_array($cache) && count($cache) > 0) { |
| 302: | $feed_items = $cache; |
| 303: | } |
| 304: | else { |
| 305: | $feed = is_news_feed($feed_data['server']); |
| 306: | if ($feed && $feed->parsed_data) { |
| 307: | $feed_items = $feed->parsed_data; |
| 308: | $cache = false; |
| 309: | } |
| 310: | } |
| 311: | } |
| 312: | $title = false; |
| 313: | foreach ($feed_items as $item) { |
| 314: | if (isset($item['id']) && !isset($item['guid'])) { |
| 315: | $item['guid'] = $item['id']; |
| 316: | unset($item['id']); |
| 317: | } |
| 318: | elseif (array_key_exists('link', $item) && !array_key_exists('guid', $item)) { |
| 319: | $item['guid'] = $item['link']; |
| 320: | } |
| 321: | elseif (isset($item['title']) && !isset($item['guid'])) { |
| 322: | $item['guid'] = md5($item['title']); |
| 323: | } |
| 324: | if (isset($item['guid']) && md5($item['guid']) == $form['feed_uid']) { |
| 325: | if (isset($item['description'])) { |
| 326: | $content = $item['description']; |
| 327: | unset($item['description']); |
| 328: | } |
| 329: | elseif (isset($item['content'])) { |
| 330: | $content = $item['content']; |
| 331: | unset($item['content']); |
| 332: | } |
| 333: | elseif (isset($item['summary'])) { |
| 334: | $content = $item['summary']; |
| 335: | unset($item['summary']); |
| 336: | } |
| 337: | if (array_key_exists('title', $item)) { |
| 338: | $title = $item['title']; |
| 339: | } |
| 340: | $headers = $item; |
| 341: | unset($headers['title']); |
| 342: | $headers = array_merge(array('title' => $title), $headers); |
| 343: | break; |
| 344: | } |
| 345: | } |
| 346: | if ($content) { |
| 347: | feed_memcached_save($this, $feed_data, $feed_items); |
| 348: | Hm_Feed_Uid_Cache::read($form['feed_uid']); |
| 349: | $this->out('feed_message_content', $content); |
| 350: | $this->out('feed_message_headers', $headers); |
| 351: | } |
| 352: | } |
| 353: | } |
| 354: | } |
| 355: | |
| 356: | |
| 357: | |
| 358: | |
| 359: | class Hm_Handler_process_add_feed extends Hm_Handler_Module { |
| 360: | public function process() { |
| 361: | if (isset($this->request->post['submit_feed'])) { |
| 362: | $found = false; |
| 363: | list($success, $form) = $this->process_form(array('new_feed_name', 'new_feed_address')); |
| 364: | if ($success) { |
| 365: | if (feed_exists($form['new_feed_address'])) { |
| 366: | Hm_Msgs::add(sprintf('Feed url %s already exists', $form['new_feed_address']), 'warning'); |
| 367: | } else { |
| 368: | $connection_test = address_from_url($form['new_feed_address']); |
| 369: | if ($con = @fsockopen($connection_test, 80, $errno, $errstr, 2)) { |
| 370: | $feed = is_news_feed($form['new_feed_address']); |
| 371: | if (!$feed) { |
| 372: | $feed = new Hm_Feed(); |
| 373: | $homepage = $feed->get_feed_data($form['new_feed_address']); |
| 374: | if (trim($homepage)) { |
| 375: | list($type, $href) = search_for_feeds($homepage); |
| 376: | if ($type && $href) { |
| 377: | Hm_Msgs::add('Discovered a feed at that address'); |
| 378: | $found = true; |
| 379: | } |
| 380: | else { |
| 381: | Hm_Msgs::add('Could not find an RSS or ATOM feed at that address', 'warning'); |
| 382: | } |
| 383: | } |
| 384: | else { |
| 385: | Hm_Msgs::add('Could not find a feed at that address', 'warning'); |
| 386: | } |
| 387: | } |
| 388: | else { |
| 389: | Hm_Msgs::add('Successfully connected to feed'); |
| 390: | $found = true; |
| 391: | if (mb_stristr('<feed', $feed->xml_data)) { |
| 392: | $type = 'application/atom+xml'; |
| 393: | } |
| 394: | else { |
| 395: | $type = 'application/rss+xml'; |
| 396: | } |
| 397: | $href = $form['new_feed_address']; |
| 398: | } |
| 399: | } |
| 400: | else { |
| 401: | Hm_Msgs::add(sprintf('Could not add feed: %s', $errstr), 'danger'); |
| 402: | } |
| 403: | } |
| 404: | } |
| 405: | else { |
| 406: | Hm_Msgs::add('Feed Name and Address are required', 'warning'); |
| 407: | } |
| 408: | if ($found) { |
| 409: | $this->out('reload_folders', true); |
| 410: | Hm_Feed_List::add(array( |
| 411: | 'name' => $form['new_feed_name'], |
| 412: | 'server' => $href, |
| 413: | 'tls' => false, |
| 414: | 'port' => 80 |
| 415: | )); |
| 416: | $this->session->record_unsaved('Feed added'); |
| 417: | } |
| 418: | } |
| 419: | } |
| 420: | } |
| 421: | |
| 422: | |
| 423: | |
| 424: | |
| 425: | class Hm_Handler_load_feeds_from_config extends Hm_Handler_Module { |
| 426: | public function process() { |
| 427: | Hm_Feed_List::init($this->user_config, $this->session); |
| 428: | Hm_Feed_Uid_Cache::load($this->cache->get('feed_read_uids', array(), true)); |
| 429: | } |
| 430: | } |
| 431: | |
| 432: | |
| 433: | |
| 434: | |
| 435: | class Hm_Handler_add_feeds_to_page_data extends Hm_Handler_Module { |
| 436: | public function process() { |
| 437: | $excluded = false; |
| 438: | if ($this->get('list_path') == 'unread') { |
| 439: | $excluded = $this->user_config->get('unread_exclude_feeds_setting', DEFAULT_UNREAD_EXCLUDE_FEEDS); |
| 440: | } |
| 441: | if ($excluded) { |
| 442: | return; |
| 443: | } |
| 444: | $feeds = Hm_Feed_List::dump(); |
| 445: | if (!empty($feeds)) { |
| 446: | $this->out('feeds', $feeds); |
| 447: | } |
| 448: | } |
| 449: | } |
| 450: | |
| 451: | |
| 452: | |
| 453: | |
| 454: | class Hm_Handler_load_feeds_for_search extends Hm_Handler_Module { |
| 455: | public function process() { |
| 456: | foreach (Hm_Feed_List::dump() as $index => $vals) { |
| 457: | $this->append('data_sources', array('feeds_search_page_content', 'type' => 'feeds', 'name' => $vals['name'], 'id' => $vals['id'])); |
| 458: | } |
| 459: | |
| 460: | } |
| 461: | } |
| 462: | |
| 463: | |
| 464: | |
| 465: | |
| 466: | class Hm_Handler_load_feeds_for_message_list extends Hm_Handler_Module { |
| 467: | public function process() { |
| 468: | $server_id = false; |
| 469: | if (array_key_exists('list_path', $this->request->get)) { |
| 470: | $path = $this->request->get['list_path']; |
| 471: | if (preg_match("/^feeds_(.+)$/", $path, $matches)) { |
| 472: | $server_id = $matches[1]; |
| 473: | } |
| 474: | } |
| 475: | |
| 476: | foreach (Hm_Feed_List::dump() as $index => $vals) { |
| 477: | if ($server_id !== false && $index != $server_id) { |
| 478: | continue; |
| 479: | } |
| 480: | $this->append('data_sources', array('type' => 'feeds', 'name' => $vals['name'], 'id' => $vals['id'])); |
| 481: | } |
| 482: | } |
| 483: | } |
| 484: | |
| 485: | |
| 486: | |
| 487: | |
| 488: | class Hm_Handler_save_feeds extends Hm_Handler_Module { |
| 489: | public function process() { |
| 490: | Hm_Feed_List::save(); |
| 491: | $this->cache->set('feed_read_uids', Hm_Feed_Uid_Cache::dump(), 0, true); |
| 492: | } |
| 493: | } |
| 494: | |
| 495: | |
| 496: | |
| 497: | |
| 498: | class Hm_Handler_load_feed_folders extends Hm_Handler_Module { |
| 499: | public function process() { |
| 500: | $feeds = Hm_Feed_List::dump(); |
| 501: | $folders = array(); |
| 502: | if (!empty($feeds)) { |
| 503: | foreach ($feeds as $id => $feed) { |
| 504: | $folders[$id] = $feed['name']; |
| 505: | } |
| 506: | } |
| 507: | $this->out('feed_folders', $folders); |
| 508: | } |
| 509: | } |
| 510: | |
| 511: | |
| 512: | |
| 513: | |
| 514: | class Hm_Output_add_feed_dialog extends Hm_Output_Module { |
| 515: | protected function output() { |
| 516: | if ($this->format == 'HTML5') { |
| 517: | $count = count($this->get('feeds', array())); |
| 518: | $count = sprintf($this->trans('%d configured'), $count); |
| 519: | |
| 520: | return '<div class="feed_server_setup"> |
| 521: | <div data-target=".feeds_section" class="server_section border-bottom cursor-pointer px-1 py-3 pe-auto"> |
| 522: | <a href="#" class="pe-auto"> |
| 523: | <i class="bi bi-rss-fill me-3"></i> |
| 524: | <b> '.$this->trans('Feeds').'</b> |
| 525: | </a> |
| 526: | <div class="server_count">'.$count.'</div> |
| 527: | </div> |
| 528: | |
| 529: | <div class="feeds_section px-4 pt-3 me-0"> |
| 530: | <div class="row"> |
| 531: | <div class="col-12 col-lg-4 mb-4"> |
| 532: | <form class="add_server me-0" method="POST"> |
| 533: | <input type="hidden" name="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" /> |
| 534: | |
| 535: | <div class="subtitle mt-4">'.$this->trans('Add an RSS/ATOM Feed').'</div> |
| 536: | |
| 537: | <div class="form-floating mb-3"> |
| 538: | <input required type="text" id="new_feed_name" name="new_feed_name" class="txt_fld form-control" value="" placeholder="'.$this->trans('Feed name').'"> |
| 539: | <label class="" for="new_feed_name">'.$this->trans('Feed name').'</label> |
| 540: | </div> |
| 541: | |
| 542: | <div class="form-floating mb-3"> |
| 543: | <input required type="url" id="new_feed_address" name="new_feed_address" class="txt_fld form-control" placeholder="'.$this->trans('Site address or feed URL').'" value=""> |
| 544: | <label for="new_feed_address" class="">'.$this->trans('Site address or feed URL').'</label> |
| 545: | </div> |
| 546: | |
| 547: | <input type="submit" class="btn btn-primary px-5" value="'.$this->trans('Add').'" name="submit_feed" /> |
| 548: | </form> |
| 549: | </div></div>'; |
| 550: | } |
| 551: | } |
| 552: | } |
| 553: | |
| 554: | |
| 555: | |
| 556: | |
| 557: | |
| 558: | class Hm_Output_display_configured_feeds extends Hm_Output_Module { |
| 559: | protected function output() { |
| 560: | $res = ''; |
| 561: | if ($this->format == 'HTML5') { |
| 562: | foreach ($this->get('feeds', array()) as $index => $vals) { |
| 563: | $res .= '<div class="configured_server col-12 col-lg-4 mb-2"><div class="card card-body">'; |
| 564: | $res .= sprintf('<div class="server_title"><b>%s</b></div><div title="%s" class="server_subtitle">%s</div>', |
| 565: | $this->html_safe($vals['name']), $this->html_safe($vals['server']), $this->html_safe($vals['server'])); |
| 566: | $res .= '<form class="feed_connect d-flex gap-2" method="POST">'; |
| 567: | $res .= '<input type="hidden" name="feed_id" value="'.$this->html_safe($index).'" />'; |
| 568: | $res .= '<input type="submit" value="'.$this->trans('Test').'" class="test_feed_connect btn btn-primary btn-sm" />'; |
| 569: | $res .= '<input type="submit" value="'.$this->trans('Delete').'" class="feed_delete btn btn-outline-danger btn-sm" />'; |
| 570: | $res .= '<input type="hidden" value="ajax_feed_debug" name="hm_ajax_hook" />'; |
| 571: | $res .= '</form></div></div>'; |
| 572: | } |
| 573: | $res .= '<br class="clear_float" /></div></div>'; |
| 574: | } |
| 575: | return $res; |
| 576: | } |
| 577: | } |
| 578: | |
| 579: | |
| 580: | |
| 581: | |
| 582: | class Hm_Output_feed_ids extends Hm_Output_Module { |
| 583: | protected function output() { |
| 584: | return '<input type="hidden" class="feed_server_ids" value="'.$this->html_safe(implode(',', array_keys($this->get('feeds', array())))).'" />'; |
| 585: | } |
| 586: | } |
| 587: | |
| 588: | |
| 589: | |
| 590: | |
| 591: | class Hm_Output_filter_feed_item_content extends Hm_Output_Module { |
| 592: | protected function output() { |
| 593: | |
| 594: | if ($this->get('feed_message_headers')) { |
| 595: | $header_str = '<div class="msg_headers d-flex flex-column border-bottom border-2 border-secondary-subtle pb-3 mb-3">'; |
| 596: | foreach ($this->get('feed_message_headers', array()) as $name => $value) { |
| 597: | if (in_array($name, array('server_id', 'server_name', 'guid', 'id', 'content'), true)) { |
| 598: | continue; |
| 599: | } |
| 600: | if ($name != 'link' && $name != 'link_alternate' && !mb_strstr($value, ' ') && mb_strlen($value) > 75) { |
| 601: | $value = mb_substr($value, 0, 75).'...'; |
| 602: | } |
| 603: | if ($name == 'title') { |
| 604: | $header_str .= '<div class="header_subject d-flex justify-content-center"><h4 class="text-center mb-0 fw-bold">'.$this->html_safe($value).'</h4></div>'; |
| 605: | } |
| 606: | elseif ($name == 'link' || $name == 'link_alternate') { |
| 607: | $header_str .= '<div class="header_'.$name.' d-flex align-items-center py-1"><span class="fw-semibold me-2 text-nowrap">'.$this->trans($name).':</span><a class="text-break" data-external="true" href="'.$this->html_safe($value).'">'.$this->html_safe($value).'</a></div>'; |
| 608: | } |
| 609: | elseif ($name == 'author' || $name == 'dc:creator' || $name == 'name') { |
| 610: | $header_str .= '<div class="header_from d-flex align-items-center py-1"><span class="fw-semibold me-2 text-nowrap">'.$this->trans($name).':</span><span class="text-break">'.$this->html_safe($value).'</span></div>'; |
| 611: | } |
| 612: | elseif ($name == 'pubdate' || $name == 'dc:date') { |
| 613: | $header_str .= '<div class="header_date d-flex align-items-center py-1"><span class="fw-semibold me-2 text-nowrap">'.$this->trans($name).':</span><span class="text-break">'.$this->html_safe($value).'</span></div>'; |
| 614: | } |
| 615: | else { |
| 616: | $header_str .= '<div class="d-flex align-items-center py-1"><span class="fw-semibold me-2 text-nowrap">'.$this->trans($name).':</span><span class="text-break">'.$this->html_safe($value).'</span></div>'; |
| 617: | } |
| 618: | } |
| 619: | $header_str .= '</div>'; |
| 620: | $this->out('feed_message_content', str_replace(array('<', '>', '“'), array(' <', '> ', ' “'), $this->get('feed_message_content'))); |
| 621: | $txt = '<div class="msg_text_inner">'.format_msg_html($this->get('feed_message_content')).'</div>'; |
| 622: | $this->out('feed_msg_text', $txt); |
| 623: | $this->out('feed_msg_headers', $header_str); |
| 624: | } |
| 625: | } |
| 626: | } |
| 627: | |
| 628: | |
| 629: | |
| 630: | |
| 631: | class Hm_Output_filter_feed_list_data extends Hm_Output_Module { |
| 632: | public static function formatMessageList($mod) { |
| 633: | $res = array(); |
| 634: | $login_time = false; |
| 635: | if ($mod->get('login_time')) { |
| 636: | $login_time = $mod->get('login_time'); |
| 637: | } |
| 638: | if ($mod->get('feed_list_parent') == 'feeds' || |
| 639: | $mod->get('feed_list_parent') == 'search' || |
| 640: | $mod->get('feed_list_parent') == 'combined_inbox') { |
| 641: | $src_callback = 'feed_source_callback'; |
| 642: | } |
| 643: | else { |
| 644: | $src_callback = 'safe_output_callback'; |
| 645: | } |
| 646: | $show_icons = $mod->get('msg_list_icons'); |
| 647: | |
| 648: | foreach ($mod->get('feed_list_data', array()) as $item) { |
| 649: | $row_style = 'feeds'; |
| 650: | if (isset($item['id']) && !isset($item['guid'])) { |
| 651: | $item['guid'] = $item['id']; |
| 652: | unset($item['id']); |
| 653: | } |
| 654: | elseif (isset($item['title']) && !isset($item['guid'])) { |
| 655: | $item['guid'] = md5($item['title']); |
| 656: | } |
| 657: | if (isset($item['guid'])) { |
| 658: | if (!array_key_exists('title', $item) || !trim($item['title'])) { |
| 659: | $item['title'] = $mod->trans('[No Subject]'); |
| 660: | } |
| 661: | $icon = 'rss'; |
| 662: | $id = sprintf("feeds_%s_%s", $item['server_id'], md5($item['guid'])); |
| 663: | if (isset($item['dc:date'])) { |
| 664: | $date = display_value('dc:date', $item, 'date'); |
| 665: | $timestamp = display_value('dc:date', $item, 'time'); |
| 666: | } |
| 667: | elseif (isset($item['pubdate'])) { |
| 668: | $date = display_value('pubdate', $item, 'date'); |
| 669: | $timestamp = display_value('pubdate', $item, 'time'); |
| 670: | } |
| 671: | else { |
| 672: | $date = ''; |
| 673: | $timestamp = 0; |
| 674: | } |
| 675: | if ($date) { |
| 676: | $date = translate_time_str($date, $mod); |
| 677: | } |
| 678: | $url = '?page=message&uid='.urlencode(md5($item['guid'])).'&list_path=feeds_'.$item['server_id']; |
| 679: | if ($mod->in('feed_list_parent', array('combined_inbox', 'unread', 'feeds', 'search'))) { |
| 680: | $url .= '&list_parent='.$mod->html_safe($mod->get('feed_list_parent', '')); |
| 681: | } |
| 682: | else { |
| 683: | $url .= '&list_parent=feeds_'.$item['server_id']; |
| 684: | } |
| 685: | if ($mod->get('news_list_style')) { |
| 686: | $style = 'news'; |
| 687: | } |
| 688: | else { |
| 689: | $style = 'email'; |
| 690: | } |
| 691: | if ($mod->get('is_mobile')) { |
| 692: | $style = 'news'; |
| 693: | } |
| 694: | if (Hm_Feed_Uid_Cache::is_read(md5($item['guid']))) { |
| 695: | $flags = array(); |
| 696: | } |
| 697: | elseif (Hm_Feed_Uid_Cache::is_unread(md5($item['guid']))) { |
| 698: | $icon = 'rss_alt'; |
| 699: | $flags = array('unseen'); |
| 700: | $row_style .= ' unseen'; |
| 701: | } |
| 702: | elseif ($timestamp && $login_time && $timestamp <= $login_time) { |
| 703: | $flags = array(); |
| 704: | } |
| 705: | else { |
| 706: | $icon = 'rss_alt'; |
| 707: | $flags = array('unseen'); |
| 708: | if (mb_strpos($row_style, 'unseen') === false) { |
| 709: | $row_style .= ' unseen'; |
| 710: | } |
| 711: | } |
| 712: | $nofrom = ''; |
| 713: | if (isset($item['author'])) { |
| 714: | $from = display_value('author', $item, 'from'); |
| 715: | } |
| 716: | elseif (isset($item['name'])) { |
| 717: | $from = display_value('name', $item, 'from'); |
| 718: | } |
| 719: | elseif (isset($item['dc:creator'])) { |
| 720: | $from = display_value('dc:creator', $item, 'from'); |
| 721: | } |
| 722: | elseif ($style == 'email') { |
| 723: | $from = $mod->trans('[No From]'); |
| 724: | $nofrom = ' nofrom'; |
| 725: | } |
| 726: | else { |
| 727: | $from = ''; |
| 728: | } |
| 729: | if (!$show_icons) { |
| 730: | $icon = false; |
| 731: | } |
| 732: | $sorting_date = $item['dc:date'] ?? $item['pubdate'] ?? ''; |
| 733: | $row_style .= ' '.str_replace(' ', '_', $item['server_name']); |
| 734: | if ($style == 'news') { |
| 735: | $res[$id] = message_list_row(array( |
| 736: | array('checkbox_callback', $id), |
| 737: | array('icon_callback', $flags), |
| 738: | array('subject_callback', strip_tags($item['title']), $url, $flags, $icon), |
| 739: | array('safe_output_callback', 'source', $item['server_name']), |
| 740: | array('safe_output_callback', 'from'.$nofrom, $from), |
| 741: | array('date_callback', $date, $timestamp), |
| 742: | array('dates_holders_callback', $sorting_date, $sorting_date), |
| 743: | ), |
| 744: | $id, |
| 745: | $style, |
| 746: | $mod, |
| 747: | $row_style |
| 748: | ); |
| 749: | } |
| 750: | else { |
| 751: | $res[$id] = message_list_row(array( |
| 752: | array('checkbox_callback', $id), |
| 753: | array($src_callback, 'source', $item['server_name'], $icon, $item['server_id']), |
| 754: | array('safe_output_callback', 'from'.$nofrom, $from), |
| 755: | array('subject_callback', strip_tags($item['title']), $url, $flags), |
| 756: | array('date_callback', $date, $timestamp), |
| 757: | array('icon_callback', $flags), |
| 758: | array('dates_holders_callback', $sorting_date, $sorting_date), |
| 759: | ), |
| 760: | $id, |
| 761: | $style, |
| 762: | $mod, |
| 763: | $row_style |
| 764: | ); |
| 765: | } |
| 766: | } |
| 767: | } |
| 768: | |
| 769: | return $res; |
| 770: | } |
| 771: | |
| 772: | protected function output() { |
| 773: | $this->out('formatted_message_list', self::formatMessageList($this)); |
| 774: | } |
| 775: | } |
| 776: | |
| 777: | |
| 778: | |
| 779: | |
| 780: | class Hm_Output_filter_feed_folders extends Hm_Output_Module { |
| 781: | protected function output() { |
| 782: | $res = ''; |
| 783: | $folders = $this->get('feed_folders', array()); |
| 784: | if (is_array($folders) && !empty($folders)) { |
| 785: | if(count($this->get('feeds', array())) > 1) { |
| 786: | $res .= '<li class="menu_feeds"><a class="unread_link" href="?page=message_list&list_path=feeds">'; |
| 787: | if (!$this->get('hide_folder_icons')) { |
| 788: | $res .= '<i class="bi bi-rss-fill menu-icon"></i>'; |
| 789: | } |
| 790: | $res .= $this->trans('All'); |
| 791: | $res .= '</a> <span class="unread_feed_count"></span></li>'; |
| 792: | } |
| 793: | foreach ($this->get('feed_folders') as $id => $folder) { |
| 794: | $res .= '<li class="feeds_'.$this->html_safe($id).'">'. |
| 795: | '<a data-id="feeds_'.$this->html_safe($id).'" href="?page=message_list&list_path=feeds_'.$this->html_safe($id).'">'; |
| 796: | if (!$this->get('hide_folder_icons')) { |
| 797: | $res .= '<i class="bi bi-rss menu-icon"></i>'; |
| 798: | } |
| 799: | $res .= $this->html_safe($folder).'</a></li>'; |
| 800: | } |
| 801: | } |
| 802: | $res .= '<li class="feeds_add_new"><a href="?page=servers#feeds_section">'; |
| 803: | if (!$this->get('hide_folder_icons')) { |
| 804: | $res .= '<i class="bi bi-plus-square menu-icon"></i>'; |
| 805: | } |
| 806: | $res .= $this->trans('Add a feed').'</a></li>'; |
| 807: | if ($res) { |
| 808: | $this->append('folder_sources', array('feeds_folders', $res)); |
| 809: | } |
| 810: | return ''; |
| 811: | } |
| 812: | } |
| 813: | |
| 814: | |
| 815: | |
| 816: | |
| 817: | class Hm_Output_display_feeds_status extends Hm_Output_Module { |
| 818: | protected function output() { |
| 819: | $res = ''; |
| 820: | foreach ($this->get('feeds', array()) as $index => $vals) { |
| 821: | $res .= '<tr><td>'.$this->trans('FEED').'</td><td>'.$this->html_safe($vals['name']).'</td><td class="feeds_status_'.$index.'"></td>'. |
| 822: | '<td class="feeds_detail_'.$index.'"></td></tr>'; |
| 823: | } |
| 824: | return $res; |
| 825: | } |
| 826: | } |
| 827: | |
| 828: | |
| 829: | |
| 830: | |
| 831: | class Hm_Output_unread_feeds_included extends Hm_Output_Module { |
| 832: | protected function output() { |
| 833: | $settings = $this->get('user_settings'); |
| 834: | if (array_key_exists('unread_exclude_feeds', $settings) && $settings['unread_exclude_feeds']) { |
| 835: | $checked = ' checked="checked"'; |
| 836: | $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>'; |
| 837: | } |
| 838: | else { |
| 839: | $checked = ''; |
| 840: | $reset = ''; |
| 841: | } |
| 842: | return '<tr class="unread_setting"><td><label class="form-check-label" for="unread_exclude_feeds">'.$this->trans('Exclude unread feed items').'</label></td>'. |
| 843: | '<td><input class="form-check-input" type="checkbox" '.$checked.' value="1" id="unread_exclude_feeds" name="unread_exclude_feeds" />'.$reset.'</td></tr>'; |
| 844: | } |
| 845: | } |
| 846: | |
| 847: | |
| 848: | |
| 849: | |
| 850: | class Hm_Output_filter_feed_status_data extends Hm_Output_Module { |
| 851: | protected function output() { |
| 852: | if ($this->get('feed_connect_status') == 'Connected') { |
| 853: | $this->out('feed_status_display', '<span class="online">'. |
| 854: | $this->trans(ucwords($this->get('feed_connect_status'))).'</span> in '.round($this->get('feed_connect_time'), 3)); |
| 855: | } |
| 856: | else { |
| 857: | $this->out('feed_status_display', '<span class="down">'.$this->trans('Down').'</span>'); |
| 858: | } |
| 859: | } |
| 860: | } |
| 861: | |
| 862: | |
| 863: | |
| 864: | |
| 865: | class Hm_Output_start_feed_settings extends Hm_Output_Module { |
| 866: | protected function output() { |
| 867: | return '<tr><td colspan="2" data-target=".feeds_setting" class="settings_subtitle cursor-pointer border-bottom p-2">'. |
| 868: | '<i class="bi bi-rss-fill menu-icon"></i>'.$this->trans('Feed Settings').'</td></tr>'; |
| 869: | } |
| 870: | } |
| 871: | |
| 872: | |
| 873: | |
| 874: | |
| 875: | class Hm_Output_feed_since_setting extends Hm_Output_Module { |
| 876: | protected function output() { |
| 877: | $since = DEFAULT_FEED_SINCE; |
| 878: | $settings = $this->get('user_settings'); |
| 879: | if (array_key_exists('feed_since', $settings)) { |
| 880: | $since = $settings['feed_since']; |
| 881: | } |
| 882: | return '<tr class="feeds_setting"><td><label for="feed_since">'.$this->trans('Show feed items received since').'</label></td>'. |
| 883: | '<td>'.message_since_dropdown($since, 'feed_since', $this, DEFAULT_FEED_SINCE).'</td></tr>'; |
| 884: | } |
| 885: | } |
| 886: | |
| 887: | |
| 888: | |
| 889: | |
| 890: | class Hm_Output_feed_limit_setting extends Hm_Output_Module { |
| 891: | protected function output() { |
| 892: | $limit = DEFAULT_FEED_LIMIT; |
| 893: | $settings = $this->get('user_settings'); |
| 894: | $reset = ''; |
| 895: | if (array_key_exists('feed_limit', $settings)) { |
| 896: | $limit = $settings['feed_limit']; |
| 897: | } |
| 898: | if ($limit != DEFAULT_FEED_LIMIT) { |
| 899: | $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>'; |
| 900: | } |
| 901: | return '<tr class="feeds_setting"><td><label for="feed_limit">'.$this->trans('Max feed items to display').'</label></td>'. |
| 902: | '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" id="feed_limit" name="feed_limit" size="2" value="'.$this->html_safe($limit).'" data-default-value="'.DEFAULT_FEED_LIMIT.'" />'.$reset.'</td></tr>'; |
| 903: | } |
| 904: | } |
| 905: | |
| 906: | |
| 907: | |
| 908: | |
| 909: | if (!hm_exists('feed_source_callback')) { |
| 910: | function feed_source_callback($vals, $style, $output_mod) { |
| 911: | if ($vals[2]) { |
| 912: | $img = '<i class="bi bi-rss-fill"></i>'; |
| 913: | } |
| 914: | else { |
| 915: | $img = ''; |
| 916: | } |
| 917: | if ($style == 'email') { |
| 918: | return sprintf('<td class="%s" title="%s"><a href="?page=message_list&list_path=feeds_%s">%s%s</td>', |
| 919: | $output_mod->html_safe($vals[0]), $output_mod->html_safe($vals[1]), $output_mod->html_safe($vals[3]), |
| 920: | $img, $output_mod->html_safe($vals[1])); |
| 921: | } |
| 922: | elseif ($style == 'news') { |
| 923: | return sprintf('<div class="%s" title="%s"><a href="?page=message_list&list_path=feeds_%s">%s%s</div>', |
| 924: | $output_mod->html_safe($vals[0]), $output_mod->html_safe($vals[1]), $output_mod->html_safe($vals[3]), |
| 925: | $img, $output_mod->html_safe($vals[1])); |
| 926: | } |
| 927: | }} |
| 928: | |
| 929: | |
| 930: | |
| 931: | |
| 932: | if (!hm_exists('address_from_url')) { |
| 933: | function address_from_url($str) { |
| 934: | $res = $str; |
| 935: | $url_bits = parse_url($str); |
| 936: | if (isset($url_bits['scheme']) && isset($url_bits['host'])) { |
| 937: | $res = $url_bits['host']; |
| 938: | } |
| 939: | return $res; |
| 940: | }} |
| 941: | |
| 942: | |
| 943: | |
| 944: | |
| 945: | if (!hm_exists('is_news_feed')) { |
| 946: | function is_news_feed($url, $limit=20) { |
| 947: | $feed = new Hm_Feed(); |
| 948: | $feed->limit = $limit; |
| 949: | $feed->parse_feed($url); |
| 950: | $feed_data = array_filter($feed->parsed_data); |
| 951: | if (empty($feed_data)) { |
| 952: | return false; |
| 953: | } |
| 954: | else { |
| 955: | return $feed; |
| 956: | } |
| 957: | }} |
| 958: | |
| 959: | |
| 960: | |
| 961: | |
| 962: | if (!hm_exists('search_for_feeds')) { |
| 963: | function search_for_feeds($html) { |
| 964: | $type = false; |
| 965: | $href = false; |
| 966: | if (preg_match_all("/<link.+>/U", $html, $matches)) { |
| 967: | foreach ($matches[0] as $link_tag) { |
| 968: | if (mb_stristr($link_tag, 'alternate')) { |
| 969: | if (preg_match("/type=(\"|'|)(.+)(\"|'|\>| )/U", $link_tag, $types)) { |
| 970: | $type = trim($types[2]); |
| 971: | } |
| 972: | if (preg_match("/href=(\"|'|)(.+)(\"|'|\>| )/U", $link_tag, $hrefs)) { |
| 973: | $href = trim($hrefs[2]); |
| 974: | } |
| 975: | } |
| 976: | } |
| 977: | } |
| 978: | return array($type, $href); |
| 979: | }} |
| 980: | |
| 981: | |
| 982: | |
| 983: | |
| 984: | if (!hm_exists('search_feed_item')) { |
| 985: | function search_feed_item($item, $terms, $since, $fld) { |
| 986: | if (array_key_exists('pubdate', $item)) { |
| 987: | if (strtotime($item['pubdate']) < strtotime($since)) { |
| 988: | return false; |
| 989: | } |
| 990: | } |
| 991: | if (array_key_exists('dc:date', $item)) { |
| 992: | if (strtotime($item['dc:date']) < strtotime($since)) { |
| 993: | return false; |
| 994: | } |
| 995: | } |
| 996: | switch ($fld) { |
| 997: | case 'BODY': |
| 998: | $flds = array('description'); |
| 999: | break; |
| 1000: | case 'FROM': |
| 1001: | $flds = array('dc:creator'); |
| 1002: | break; |
| 1003: | case 'SUBJECT': |
| 1004: | $flds = array('title'); |
| 1005: | break; |
| 1006: | case 'TEXT': |
| 1007: | default: |
| 1008: | $flds = array('description', 'title', 'dc:creator', 'guid'); |
| 1009: | break; |
| 1010: | } |
| 1011: | foreach ($flds as $fld) { |
| 1012: | if (array_key_exists($fld, $item)) { |
| 1013: | if (mb_stristr($item[$fld], $terms) !== false) { |
| 1014: | return true; |
| 1015: | } |
| 1016: | } |
| 1017: | } |
| 1018: | return false; |
| 1019: | }} |
| 1020: | |
| 1021: | |
| 1022: | |
| 1023: | |
| 1024: | if (!hm_exists('feed_memcached_save')) { |
| 1025: | function feed_memcached_save($hmod, $feed_data, $data) { |
| 1026: | $key = sprintf('%s%s%s', $feed_data['server'], $feed_data['tls'], $feed_data['port']); |
| 1027: | $hmod->cache->set($key, $data, 500); |
| 1028: | }} |
| 1029: | |
| 1030: | |
| 1031: | |
| 1032: | |
| 1033: | if (!hm_exists('feed_memcached_fetch')) { |
| 1034: | function feed_memcached_fetch($hmod, $feed_data) { |
| 1035: | $key = sprintf('%s%s%s', $feed_data['server'], $feed_data['tls'], $feed_data['port']); |
| 1036: | return $hmod->cache->get($key); |
| 1037: | }} |
| 1038: | |
| 1039: | |
| 1040: | |
| 1041: | |
| 1042: | if (!hm_exists('feed_exists')) { |
| 1043: | function feed_exists($server) { |
| 1044: | $list = Hm_Feed_List::dump(); |
| 1045: | foreach ($list as $feed) { |
| 1046: | if ($feed['server'] == $server) { |
| 1047: | return true; |
| 1048: | } |
| 1049: | } |
| 1050: | return false; |
| 1051: | }} |
| 1052: | |