emails.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. <?php
  2. defined('ABSPATH') || exit;
  3. require_once NEWSLETTER_INCLUDES_DIR . '/themes.php';
  4. require_once NEWSLETTER_INCLUDES_DIR . '/module.php';
  5. class NewsletterEmails extends NewsletterModule {
  6. static $instance;
  7. const EDITOR_COMPOSER = 2;
  8. const EDITOR_HTML = 1;
  9. const EDITOR_TINYMCE = 0;
  10. static $PRESETS_LIST;
  11. /**
  12. * @return NewsletterEmails
  13. */
  14. static function instance() {
  15. if (self::$instance == null) {
  16. self::$instance = new NewsletterEmails();
  17. }
  18. return self::$instance;
  19. }
  20. function __construct() {
  21. self::$PRESETS_LIST = array("cta", "invite", "announcement", "posts", "sales", "product", "tour", "simple", "blank");
  22. $this->themes = new NewsletterThemes('emails');
  23. parent::__construct('emails', '1.1.5');
  24. add_action('wp_loaded', array($this, 'hook_wp_loaded'));
  25. if (is_admin()) {
  26. add_action('wp_ajax_tnpc_render', array($this, 'tnpc_render_callback'));
  27. add_action('wp_ajax_tnpc_preview', array($this, 'tnpc_preview_callback'));
  28. add_action('wp_ajax_tnpc_css', array($this, 'tnpc_css_callback'));
  29. add_action('wp_ajax_tnpc_options', array($this, 'hook_wp_ajax_tnpc_options'));
  30. add_action('wp_ajax_tnpc_presets', array($this, 'hook_wp_ajax_tnpc_presets'));
  31. // Thank you to plugins which add the WP editor on other admin plugin pages...
  32. if (isset($_GET['page']) && $_GET['page'] == 'newsletter_emails_edit') {
  33. global $wp_actions;
  34. $wp_actions['wp_enqueue_editor'] = 1;
  35. }
  36. }
  37. }
  38. function options_decode($options) {
  39. // Start compatibility
  40. if (is_string($options) && strpos($options, 'options[') !== false) {
  41. $opts = array();
  42. parse_str($options, $opts);
  43. $options = $opts['options'];
  44. }
  45. // End compatibility
  46. if (is_array($options)) {
  47. return $options;
  48. }
  49. $tmp = json_decode($options, true);
  50. if (is_null($tmp)) {
  51. return json_decode(base64_decode($options), true);
  52. } else {
  53. return $tmp;
  54. }
  55. }
  56. /**
  57. *
  58. * @param array $options Options array
  59. */
  60. function options_encode($options) {
  61. return base64_encode(json_encode($options, JSON_HEX_TAG | JSON_HEX_AMP));
  62. }
  63. function hook_wp_ajax_tnpc_options() {
  64. global $wpdb;
  65. // TODO: Uniform to use id everywhere
  66. if (!isset($_REQUEST['id']))
  67. $_REQUEST['id'] = $_REQUEST['b'];
  68. $block = $this->get_block($_REQUEST['id']);
  69. if (!$block) {
  70. die('Block not found with id ' . esc_html($_REQUEST['id']));
  71. }
  72. if (!class_exists('NewsletterControls')) {
  73. include NEWSLETTER_INCLUDES_DIR . '/controls.php';
  74. }
  75. $options = $this->options_decode(stripslashes_deep($_REQUEST['options']));
  76. $context = array('type'=>'');
  77. if (isset($_REQUEST['context_type'])) $context['type'] = $_REQUEST['context_type'];
  78. // $defaults = array(
  79. // 'block_padding_top' => 15,
  80. // 'block_padding_bottom' => 15,
  81. // 'block_padding_right' => 0,
  82. // 'block_padding_left' => 0,
  83. // 'block_background' => '#ffffff'
  84. // );
  85. //
  86. // $options = array_merge($defaults, $options);
  87. $controls = new NewsletterControls($options);
  88. $fields = new NewsletterFields($controls);
  89. $controls->init();
  90. echo '<input type="hidden" name="action" value="tnpc_render">';
  91. echo '<input type="hidden" name="b" value="' . esc_attr($_REQUEST['id']) . '">';
  92. ob_start();
  93. include $block['dir'] . '/options.php';
  94. $content = ob_get_clean();
  95. echo "<h2>", esc_html($block["name"]), "</h2>";
  96. echo $content;
  97. wp_die();
  98. }
  99. /**
  100. * Retrieves the presets list (no id in GET) or a specific preset id in GET)
  101. *
  102. * @return string
  103. */
  104. function hook_wp_ajax_tnpc_presets() {
  105. $content = "";
  106. if (!empty($_REQUEST['id'])) {
  107. // Preset render
  108. $preset = $this->get_preset($_REQUEST['id']);
  109. foreach ($preset->blocks as $item) {
  110. $this->render_block($item->block, true, (array) $item->options);
  111. }
  112. } else {
  113. $content = "<div class='clear tnpc-presets-title'>" . __('Choose a preset:', 'newsletter') . "</div>";
  114. foreach (self::$PRESETS_LIST as $id) {
  115. $preset = $this->get_preset($id);
  116. $content .= "<div class='tnpc-preset' onclick='tnpc_load_preset(\"$id\")'>";
  117. $content .= "<img src='$preset->icon' title='$preset->name' />";
  118. $content .= "<span class='tnpc-preset-label'>$preset->name</span>";
  119. $content .= '</div>';
  120. }
  121. $content .= '<div class="clear"></div>';
  122. echo $content;
  123. }
  124. wp_die();
  125. }
  126. function has_dynamic_blocks($theme) {
  127. preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
  128. foreach ($matches[1] as $match) {
  129. $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
  130. $options = $this->options_decode($a);
  131. $block = $this->get_block($options['block_id']);
  132. if (!$block) {
  133. continue;
  134. }
  135. if ($block['type'] == 'dynamic')
  136. return true;
  137. }
  138. return false;
  139. }
  140. /**
  141. * Regenerates a saved composed email rendering each block. Regeneration is
  142. * conditioned (possibly) by the context. The context is usually passed to blocks
  143. * so they can act in the right manner.
  144. *
  145. * The last run parameter can instruct the block to generate content conditioned to
  146. * the passed timestamp (for example limiting the content to new posts from the last
  147. * run timestamp).
  148. *
  149. * @param string $theme (Rinominare)
  150. * @return string
  151. */
  152. function regenerate($theme, $context = array()) {
  153. $this->logger->debug('Starting email regeneration');
  154. $this->logger->debug($context);
  155. if (empty($theme)) {
  156. $this->logger->debug('The email was empty');
  157. return array('body' => '', 'subject' => '');
  158. }
  159. $context = array_merge(array('last_run' => 0, 'type' => ''), $context);
  160. preg_match_all('/data-json="(.*?)"/m', $theme, $matches, PREG_PATTERN_ORDER);
  161. $result = '';
  162. $all_empty = true; // If all dynamic content blocks return an empty html
  163. $has_dynamic_blocks = false;
  164. $subject = '';
  165. foreach ($matches[1] as $match) {
  166. $a = html_entity_decode($match, ENT_QUOTES, 'UTF-8');
  167. $options = $this->options_decode($a);
  168. $block = $this->get_block($options['block_id']);
  169. if (!$block) {
  170. $this->logger->debug('Unable to load the block ' . $options['block_id']);
  171. continue;
  172. }
  173. ob_start();
  174. $out = $this->render_block($options['block_id'], true, $options, $context);
  175. if ($out['return_empty_message']) {
  176. return '';
  177. }
  178. if (empty($subject) && !empty($out['subject'])) {
  179. $subject = $out['subject'];
  180. }
  181. $block_html = ob_get_clean();
  182. $result .= $block_html;
  183. // If a dynamic block return something, we need to return a regenerated template
  184. if ($block['type'] == 'dynamic') {
  185. $has_dynamic_blocks = true;
  186. if (!empty($block_html)) {
  187. $all_empty = false;
  188. }
  189. }
  190. }
  191. if (!empty($context['last_run']) && $has_dynamic_blocks && $all_empty) {
  192. return '';
  193. }
  194. $x = strpos($theme, '<body');
  195. if ($x !== false) {
  196. $x = strpos($theme, '>', $x);
  197. $result = substr($theme, 0, $x + 1) . $result . '</body></html>';
  198. } else {
  199. }
  200. return array('body' => $result, 'subject' => $subject);
  201. }
  202. function remove_block_data($text) {
  203. // TODO: Lavorare!
  204. return $text;
  205. }
  206. /**
  207. * Renders a block identified by its id, using the block options and adding a wrapper
  208. * if required (for the first block rendering.
  209. * @param type $block_id
  210. * @param type $wrapper
  211. * @param type $options
  212. */
  213. function render_block($block_id = null, $wrapper = false, $options = array(), $context = array()) {
  214. include_once NEWSLETTER_INCLUDES_DIR . '/helper.php';
  215. $width = 600;
  216. $font_family = 'Helvetica, Arial, sans-serif';
  217. $info = Newsletter::instance()->get_options('info');
  218. // Just in case...
  219. if (!is_array($options)) {
  220. $options = array();
  221. }
  222. $block_options = get_option('newsletter_main');
  223. $block = $this->get_block($block_id);
  224. // Block not found
  225. if (!$block) {
  226. if ($wrapper) {
  227. echo '<table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">';
  228. echo '<tr>';
  229. echo '<td data-options="" bgcolor="#ffffff" align="center" style="padding: 0; font-family: Helvetica, Arial, sans-serif;" class="edit-block">';
  230. }
  231. echo '<!--[if mso]><table border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '"><![endif]-->';
  232. echo "\n";
  233. echo 'Block not found';
  234. echo "<!--[if mso]></td></tr></table><![endif]-->\n";
  235. if ($wrapper) {
  236. echo '</td></tr></table>';
  237. }
  238. return;
  239. }
  240. $out = array('subject' => '', 'return_empty_message'=>false);
  241. ob_start();
  242. $logger = $this->logger;
  243. include $block['dir'] . '/block.php';
  244. $content = trim(ob_get_clean());
  245. if (empty($content)) {
  246. return $out;
  247. }
  248. $common_defaults = array(
  249. 'block_padding_top' => 0,
  250. 'block_padding_bottom' => 0,
  251. 'block_padding_right' => 0,
  252. 'block_padding_left' => 0,
  253. 'block_background' => '#ffffff'
  254. );
  255. $options = array_merge($common_defaults, $options);
  256. // Obsolete
  257. $content = str_replace('{width}', $width, $content);
  258. $content = $this->inline_css($content, true);
  259. // CSS driven by the block
  260. // Requited for the server side parsing and rendering
  261. $options['block_id'] = $block_id;
  262. $options['block_padding_top'] = (int) str_replace('px', '', $options['block_padding_top']);
  263. $options['block_padding_bottom'] = (int) str_replace('px', '', $options['block_padding_bottom']);
  264. $options['block_padding_right'] = (int) str_replace('px', '', $options['block_padding_right']);
  265. $options['block_padding_left'] = (int) str_replace('px', '', $options['block_padding_left']);
  266. // Internal TD wrapper
  267. $style = 'text-align: center; ';
  268. $style .= 'width: 100%!important; ';
  269. $style .= 'padding-top: ' . $options['block_padding_top'] . 'px; ';
  270. $style .= 'padding-left: ' . $options['block_padding_left'] . 'px; ';
  271. $style .= 'padding-right: ' . $options['block_padding_right'] . 'px; ';
  272. $style .= 'padding-bottom: ' . $options['block_padding_bottom'] . 'px; ';
  273. $style .= 'background-color: ' . $options['block_background'] . ';';
  274. $data = $this->options_encode($options);
  275. // First time block creation wrapper
  276. if ($wrapper) {
  277. echo '<table type="block" border="0" cellpadding="0" cellspacing="0" align="center" width="100%" style="border-collapse: collapse; width: 100%;" class="tnpc-row tnpc-row-block" data-id="', esc_attr($block_id), '">', "\n";
  278. echo "<tr>";
  279. echo '<td align="center" style="padding: 0;" class="edit-block">', "\n";
  280. }
  281. // Container that fixes the width and makes the block responsive
  282. echo '<!--[if mso]><table border="0" cellpadding="0" align="center" cellspacing="0" width="' . $width . '"><tr><td width="' . $width . '"><![endif]-->';
  283. echo "\n";
  284. echo '<table type="options" data-json="', esc_attr($data), '" class="tnpc-block-content" border="0" cellpadding="0" align="center" cellspacing="0" width="100%" style="width: 100%!important; max-width: ', $width, 'px!important">', "\n";
  285. echo "<tr>";
  286. echo '<td align="center" style="', $style, '" bgcolor="', $options['block_background'], '" width="100%">', "\n";
  287. //echo "<!-- block generated content -->\n";
  288. echo $content;
  289. //echo "\n<!-- /block generated content -->\n";
  290. echo "\n</td></tr></table>";
  291. echo '<!--[if mso]></td></tr></table><![endif]-->';
  292. // First time block creation wrapper
  293. if ($wrapper) {
  294. echo "</td></tr></table>";
  295. }
  296. echo "\n";
  297. return $out;
  298. }
  299. /**
  300. * Ajax call to render a block with a new set of options after the settings popup
  301. * has been saved.
  302. *
  303. * @param type $block_id
  304. * @param type $wrapper
  305. */
  306. function tnpc_render_callback() {
  307. $block_id = $_POST['b'];
  308. $wrapper = isset($_POST['full']);
  309. if (isset($_POST['options']) && is_array($_POST['options'])) {
  310. $options = stripslashes_deep($_POST['options']);
  311. } else {
  312. $options = array();
  313. }
  314. $this->render_block($block_id, $wrapper, $options);
  315. wp_die();
  316. }
  317. function tnpc_preview_callback() {
  318. $email = Newsletter::instance()->get_email($_REQUEST['id'], ARRAY_A);
  319. if (empty($email)) {
  320. echo 'Wrong email identifier';
  321. return;
  322. }
  323. echo $email['message'];
  324. wp_die(); // this is required to terminate immediately and return a proper response
  325. }
  326. function tnpc_css_callback() {
  327. include NEWSLETTER_DIR . '/emails/tnp-composer/css/newsletter.css';
  328. wp_die(); // this is required to terminate immediately and return a proper response
  329. }
  330. /** Returns the correct admin page to edit the newsletter with the correct editor. */
  331. function get_editor_url($email_id, $editor_type) {
  332. switch ($editor_type) {
  333. case NewsletterEmails::EDITOR_COMPOSER: return admin_url("admin.php") . '?page=newsletter_emails_composer&id=' . $email_id;
  334. case NewsletterEmails::EDITOR_HTML: return admin_url("admin.php") . '?page=newsletter_emails_editorhtml&id=' . $email_id;
  335. case NewsletterEmails::EDITOR_TINYMCE: return admin_url("admin.php") . '?page=newsletter_emails_editortinymce&id=' . $email_id;
  336. }
  337. }
  338. /**
  339. * Returns the button linked to the correct "edit" page for the passed newsletter. The edit page can be an editor
  340. * or the targeting page (it depends on newsletter status).
  341. *
  342. * @param TNP_Email $email
  343. */
  344. function get_edit_button($email) {
  345. $editor_type = $this->get_editor_type($email);
  346. if ($email->status == 'new') {
  347. $edit_url = $this->get_editor_url($email->id, $editor_type);
  348. } else {
  349. $edit_url = 'admin.php?page=newsletter_emails_edit&id=' . $email->id;
  350. }
  351. switch ($editor_type) {
  352. case NewsletterEmails::EDITOR_COMPOSER:
  353. $icon_class = 'th-large';
  354. break;
  355. case NewsletterEmails::EDITOR_HTML:
  356. $icon_class = 'code';
  357. break;
  358. default:
  359. $icon_class = 'edit';
  360. break;
  361. }
  362. return '<a class="button-primary" href="' . $edit_url . '">' .
  363. '<i class="fa fa-' . $icon_class . '"></i> ' . __('Edit', 'newsletter') . '</a>';
  364. }
  365. /** Returns the correct editor type for the provided newsletter. Contains backward compatibility code. */
  366. function get_editor_type($email) {
  367. $email = (object) $email;
  368. $editor_type = $email->editor;
  369. // Backward compatibility
  370. $email_options = maybe_unserialize($email->options);
  371. if (isset($email_options['composer'])) {
  372. $editor_type = NewsletterEmails::EDITOR_COMPOSER;
  373. }
  374. // End backward compatibility
  375. return $editor_type;
  376. }
  377. function hook_wp_loaded() {
  378. global $wpdb;
  379. $newsletter = Newsletter::instance();
  380. switch ($newsletter->action) {
  381. case 'v':
  382. case 'view':
  383. $email = $this->get_email($_GET['id']);
  384. if (empty($email)) {
  385. header("HTTP/1.0 404 Not Found");
  386. die('Email not found');
  387. }
  388. $user = NewsletterSubscription::instance()->get_user_from_request();
  389. if (!is_user_logged_in() || !(current_user_can('editor') || current_user_can('administrator'))) {
  390. if ($email->status == 'new') {
  391. header("HTTP/1.0 404 Not Found");
  392. die('Not sent yet');
  393. }
  394. if ($email->private == 1) {
  395. if (!$user) {
  396. header("HTTP/1.0 404 Not Found");
  397. die('No available for online view');
  398. }
  399. $sent = $wpdb->get_row($wpdb->prepare("select * from " . NEWSLETTER_SENT_TABLE . " where email_id=%d and user_id=%d limit 1", $email->id, $user->id));
  400. if (!$sent) {
  401. header("HTTP/1.0 404 Not Found");
  402. die('No available for online view');
  403. }
  404. }
  405. }
  406. header('Content-Type: text/html;charset=UTF-8');
  407. header('X-Robots-Tag: noindex,nofollow,noarchive');
  408. header('Cache-Control: no-cache,no-store,private');
  409. echo $newsletter->replace($email->message, $user, $email);
  410. die();
  411. break;
  412. case 'emails-css':
  413. $email_id = (int) $_GET['id'];
  414. $body = Newsletter::instance()->get_email_field($email_id, 'message');
  415. $x = strpos($body, '<style');
  416. if ($x === false)
  417. return;
  418. $x = strpos($body, '>', $x);
  419. $y = strpos($body, '</style>');
  420. header('Content-Type: text/css;charset=UTF-8');
  421. echo substr($body, $x + 1, $y - $x - 1);
  422. die();
  423. break;
  424. case 'emails-composer-css':
  425. header('Cache: no-cache');
  426. header('Content-Type: text/css');
  427. echo $this->get_composer_css();
  428. die();
  429. break;
  430. case 'emails-preview':
  431. if (!Newsletter::instance()->is_allowed()) {
  432. die('Not enough privileges');
  433. }
  434. if (!check_admin_referer('view')) {
  435. die();
  436. }
  437. // Used by theme code
  438. $theme_options = $this->get_current_theme_options();
  439. $theme_url = $this->get_current_theme_url();
  440. header('Content-Type: text/html;charset=UTF-8');
  441. include($this->get_current_theme_file_path('theme.php'));
  442. die();
  443. break;
  444. case 'emails-preview-text':
  445. header('Content-Type: text/plain;charset=UTF-8');
  446. if (!Newsletter::instance()->is_allowed()) {
  447. die('Not enough privileges');
  448. }
  449. if (!check_admin_referer('view')) {
  450. die();
  451. }
  452. // Used by theme code
  453. $theme_options = $this->get_current_theme_options();
  454. $file = $this->get_current_theme_file_path('theme-text.php');
  455. if (is_file($file)) {
  456. include($this->get_current_theme_file_path('theme-text.php'));
  457. }
  458. die();
  459. break;
  460. case 'emails-create':
  461. if (!Newsletter::instance()->is_allowed()) {
  462. die('Not enough privileges');
  463. }
  464. require_once NEWSLETTER_INCLUDES_DIR . '/controls.php';
  465. $controls = new NewsletterControls();
  466. if ($controls->is_action('create')) {
  467. $this->save_options($controls->data);
  468. $email = array();
  469. $email['status'] = 'new';
  470. $email['subject'] = ''; //__('Here the email subject', 'newsletter');
  471. $email['track'] = 1;
  472. $theme_options = $this->get_current_theme_options();
  473. $theme_url = $this->get_current_theme_url();
  474. $theme_subject = '';
  475. ob_start();
  476. include $this->get_current_theme_file_path('theme.php');
  477. $email['message'] = ob_get_clean();
  478. if (!empty($theme_subject)) {
  479. $email['subject'] = $theme_subject;
  480. }
  481. ob_start();
  482. include $this->get_current_theme_file_path('theme-text.php');
  483. $email['message_text'] = ob_get_clean();
  484. $email['type'] = 'message';
  485. $email['send_on'] = time();
  486. $email = $newsletter->save_email($email);
  487. $edit_url = $this->get_editor_url($email->id, $email->editor);
  488. header('Location: ' . $edit_url);
  489. }
  490. die();
  491. break;
  492. }
  493. }
  494. function upgrade() {
  495. global $wpdb, $charset_collate;
  496. parent::upgrade();
  497. $this->upgrade_query("alter table " . NEWSLETTER_EMAILS_TABLE . " change column `type` `type` varchar(50) not null default ''");
  498. $this->upgrade_query("alter table " . NEWSLETTER_EMAILS_TABLE . " add column token varchar(10) not null default ''");
  499. $this->upgrade_query("alter table " . NEWSLETTER_EMAILS_TABLE . " drop column visibility");
  500. $this->upgrade_query("alter table " . NEWSLETTER_EMAILS_TABLE . " add column private tinyint(1) not null default 0");
  501. // Force a token to email without one already set.
  502. //$token = self::get_token();
  503. //$wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set token='" . $token . "' where token=''");
  504. if ($this->old_version < '1.1.5') {
  505. $this->upgrade_query("update " . NEWSLETTER_EMAILS_TABLE . " set type='message' where type=''");
  506. $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set token=''");
  507. }
  508. $wpdb->query("update " . NEWSLETTER_EMAILS_TABLE . " set total=sent where status='sent' and type='message'");
  509. return true;
  510. }
  511. function admin_menu() {
  512. $this->add_menu_page('index', 'Newsletters');
  513. $this->add_admin_page('list', 'Email List');
  514. $this->add_admin_page('new', 'Email New');
  515. $this->add_admin_page('edit', 'Email Edit');
  516. $this->add_admin_page('theme', 'Email Themes');
  517. $this->add_admin_page('composer', 'The Composer');
  518. $this->add_admin_page('editorhtml', 'HTML Editor');
  519. $this->add_admin_page('editortinymce', 'TinyMCE Editor');
  520. //$this->add_admin_page('cpreview', 'The Composer Preview');
  521. }
  522. /**
  523. * Returns the current selected theme.
  524. */
  525. function get_current_theme() {
  526. $theme = $this->options['theme'];
  527. if (empty($theme))
  528. return 'blank';
  529. else
  530. return $theme;
  531. }
  532. function get_current_theme_options() {
  533. $theme_options = $this->themes->get_options($this->get_current_theme());
  534. // main options merge
  535. $main_options = Newsletter::instance()->options;
  536. foreach ($main_options as $key => $value) {
  537. $theme_options['main_' . $key] = $value;
  538. }
  539. $info_options = Newsletter::instance()->get_options('info');
  540. foreach ($info_options as $key => $value) {
  541. $theme_options['main_' . $key] = $value;
  542. }
  543. return $theme_options;
  544. }
  545. /**
  546. * Returns the file path to a theme using the theme overriding rules.
  547. * @param type $theme
  548. * @param type $file
  549. */
  550. function get_theme_file_path($theme, $file) {
  551. return $this->themes->get_file_path($theme);
  552. }
  553. function get_current_theme_file_path($file) {
  554. return $this->themes->get_file_path($this->get_current_theme(), $file);
  555. }
  556. function get_current_theme_url() {
  557. return $this->themes->get_theme_url($this->get_current_theme());
  558. }
  559. /**
  560. * Returns true if the emails database still contain old 2.5 format emails.
  561. *
  562. * @return boolean
  563. */
  564. function has_old_emails() {
  565. return $this->store->get_count(NEWSLETTER_EMAILS_TABLE, "where type='email'") > 0;
  566. }
  567. function convert_old_emails() {
  568. global $newsletter;
  569. $list = $newsletter->get_emails('email', ARRAY_A);
  570. foreach ($list as &$email) {
  571. $email['type'] = 'message';
  572. $query = "select * from " . NEWSLETTER_USERS_TABLE . " where status='C'";
  573. if ($email['list'] != 0)
  574. $query .= " and list_" . $email['list'] . "=1";
  575. $email['preferences'] = $email['list'];
  576. if (!empty($email['sex'])) {
  577. $query .= " and sex='" . $email['sex'] . "'";
  578. }
  579. $email['query'] = $query;
  580. $newsletter->save_email($email);
  581. }
  582. }
  583. function build_block($dir) {
  584. $file = basename($dir);
  585. $block_id = sanitize_key($file);
  586. $full_file = $dir . '/block.php';
  587. if (!is_file($full_file)) {
  588. return new WP_Error('1', 'Missing block.php file in ' . $dir);
  589. }
  590. if (!is_file($dir . '/icon.png')) {
  591. $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
  592. $data['icon'] = content_url($relative_dir . '/icon.png');
  593. }
  594. $data = get_file_data($full_file, array('name' => 'Name', 'section' => 'Section', 'description' => 'Description', 'type' => 'Type'));
  595. $defaults = array('section' => 'content', 'name' => $file, 'descritpion' => '', 'icon' => NEWSLETTER_URL . '/images/block-icon.png', 'content' => '');
  596. $data = array_merge($defaults, $data);
  597. if (is_file($dir . '/icon.png')) {
  598. $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
  599. $data['icon'] = content_url($relative_dir . '/icon.png');
  600. }
  601. $data['id'] = $block_id;
  602. // Absolute path of the block files
  603. $data['dir'] = $dir;
  604. return $data;
  605. }
  606. function scan_blocks_dir($dir) {
  607. if (!is_dir($dir)) {
  608. return array();
  609. }
  610. $handle = opendir($dir);
  611. $list = array();
  612. $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
  613. while ($file = readdir($handle)) {
  614. if ($file == '.' || $file == '..')
  615. continue;
  616. $data = $this->build_block($dir . '/' . $file);
  617. if (is_wp_error($data)) {
  618. $this->logger->error($data);
  619. continue;
  620. }
  621. $list[$data['id']] = $data;
  622. }
  623. closedir($handle);
  624. return $list;
  625. }
  626. /**
  627. * Array of arrays with every registered block and legacy block converted to the new
  628. * format.
  629. *
  630. * @return array
  631. */
  632. function get_blocks() {
  633. static $blocks = null;
  634. if (!is_null($blocks))
  635. return $blocks;
  636. $blocks = $this->scan_blocks_dir(__DIR__ . '/blocks');
  637. $extended = $this->scan_blocks_dir(WP_CONTENT_DIR . '/extensions/newsletter/blocks');
  638. $blocks = array_merge($extended, $blocks);
  639. $dirs = apply_filters('newsletter_blocks_dir', array());
  640. $this->logger->debug('Block dirs: ' . print_r($dirs, true));
  641. foreach ($dirs as $dir) {
  642. $dir = str_replace('\\', '/', $dir);
  643. $list = $this->scan_blocks_dir($dir);
  644. $blocks = array_merge($list, $blocks);
  645. }
  646. do_action('newsletter_register_blocks');
  647. foreach (TNP_Composer::$block_dirs as $dir) {
  648. $block = $this->build_block($dir);
  649. if (is_wp_error($block)) {
  650. $this->logger->error($block);
  651. continue;
  652. }
  653. if (!isset($blocks[$block['id']])) {
  654. $blocks[$block['id']] = $block;
  655. } else {
  656. $this->logger->error('The block "' . $block['id'] . '" is already registered');
  657. }
  658. }
  659. $blocks = array_reverse($blocks);
  660. return $blocks;
  661. }
  662. /**
  663. * Return a single block (associative array) checking for legacy ID as well.
  664. *
  665. * @param string $id
  666. * @return array
  667. */
  668. function get_block($id) {
  669. switch ($id) {
  670. case 'content-03-text.block':
  671. $id = 'text';
  672. break;
  673. case 'footer-03-social.block':
  674. $id = 'social';
  675. break;
  676. case 'footer-02-canspam.block':
  677. $id = 'canspam';
  678. break;
  679. case 'content-05-image.block':
  680. $id = 'image';
  681. break;
  682. case 'header-01-header.block':
  683. $id = 'header';
  684. break;
  685. case 'footer-01-footer.block':
  686. $id = 'footer';
  687. break;
  688. case 'content-02-heading.block':
  689. $id = 'heading';
  690. break;
  691. case 'content-07-twocols.block':
  692. case 'content-06-posts.block':
  693. $id = 'posts';
  694. break;
  695. case 'content-04-cta.block': $id = 'cta';
  696. break;
  697. case 'content-01-hero.block': $id = 'hero';
  698. break;
  699. // case 'content-02-heading.block': $id = '/plugins/newsletter/emails/blocks/heading';
  700. // break;
  701. }
  702. // Conversion for old full path ID
  703. $id = sanitize_key(basename($id));
  704. // TODO: Correct id for compatibility
  705. $blocks = $this->get_blocks();
  706. if (!isset($blocks[$id])) {
  707. return null;
  708. }
  709. return $blocks[$id];
  710. }
  711. function scan_presets_dir($dir = null) {
  712. if (is_null($dir)) {
  713. $dir = __DIR__ . '/presets';
  714. }
  715. if (!is_dir($dir)) {
  716. return array();
  717. }
  718. $handle = opendir($dir);
  719. $list = array();
  720. $relative_dir = substr($dir, strlen(WP_CONTENT_DIR));
  721. while ($file = readdir($handle)) {
  722. if ($file == '.' || $file == '..')
  723. continue;
  724. // The block unique key, we should find out how to build it, maybe an hash of the (relative) dir?
  725. $preset_id = sanitize_key($file);
  726. $full_file = $dir . '/' . $file . '/preset.json';
  727. if (!is_file($full_file)) {
  728. continue;
  729. }
  730. $icon = content_url($relative_dir . '/' . $file . '/icon.png');
  731. $list[$preset_id] = $icon;
  732. }
  733. closedir($handle);
  734. return $list;
  735. }
  736. function get_preset($id, $dir = null) {
  737. if (is_null($dir)) {
  738. $dir = __DIR__ . '/presets';
  739. }
  740. $id = $this->sanitize_file_name($id);
  741. if (!is_dir($dir . '/' . $id) || !in_array($id, self::$PRESETS_LIST)) {
  742. return array();
  743. }
  744. $json_content = file_get_contents("$dir/$id/preset.json");
  745. $json_content = str_replace("{placeholder_base_url}", plugins_url('newsletter') . '/emails/presets', $json_content);
  746. $json = json_decode($json_content);
  747. $json->icon = NEWSLETTER_URL . "/emails/presets/$id/icon.png";
  748. return $json;
  749. }
  750. function get_composer_css() {
  751. $css = file_get_contents(__DIR__ . '/tnp-composer/css/newsletter.css');
  752. $blocks = $this->get_blocks();
  753. foreach ($blocks as $block) {
  754. if (!file_exists($block['dir'] . '/style.css')) {
  755. continue;
  756. }
  757. $css .= "\n\n";
  758. $css .= "/* " . $block['name'] . " */\n";
  759. $css .= file_get_contents($block['dir'] . '/style.css');
  760. }
  761. return $css;
  762. }
  763. function send_test_email($email, $controls) {
  764. if (!$email) {
  765. $controls->errors = __('Newsletter should be saved before send a test', 'newsletter');
  766. return;
  767. }
  768. if ($email->subject == '') {
  769. $email->subject = '[TEST] Dummy subject, it was empty (remember to set it)';
  770. } else {
  771. $email->subject = $email->subject . ' (TEST)';
  772. }
  773. $users = NewsletterUsers::instance()->get_test_users();
  774. if (count($users) == 0) {
  775. $controls->errors = '' . __('There are no test subscribers to send to', 'newsletter') .
  776. '. <a href="https://www.thenewsletterplugin.com/plugins/newsletter/subscribers-module#test" target="_blank"><strong>' .
  777. __('Read more', 'newsletter') . '</strong></a>.';
  778. } else {
  779. $r = Newsletter::instance()->send($email, $users, true);
  780. $emails = array();
  781. foreach ($users as $user) {
  782. $emails[] = '<a href="admin.php?page=newsletter_users_edit&id=' . $user->id . '" target="_blank">' . $user->email . '</a>';
  783. }
  784. if (is_wp_error($r)) {
  785. $controls->errors = 'Something went wrong. Check the error logs on status page.<br>';
  786. $controls->errors .= __('Test subscribers:', 'newsletter');
  787. $controls->errors .= ' ' . implode(', ', $emails);
  788. $controls->errors .= '<br>';
  789. $controls->errors .= '<strong>' . esc_html($r->get_error_message()) . '</strong><br>';
  790. $controls->errors .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
  791. } else {
  792. $controls->messages = __('Test subscribers:', 'newsletter');
  793. $controls->messages .= ' ' . implode(', ', $emails);
  794. $controls->messages .= '.<br>';
  795. $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/subscribers#test" target="_blank"><strong>' .
  796. __('Read more about test subscribers', 'newsletter') . '</strong></a>.<br>';
  797. $controls->messages .= '<a href="https://www.thenewsletterplugin.com/documentation/email-sending-issues" target="_blank"><strong>' . __('Read more about delivery issues', 'newsletter') . '</strong></a>.';
  798. }
  799. }
  800. }
  801. }
  802. NewsletterEmails::instance();