Hook up post fetching and post transformation

This commit is contained in:
Tautvidas Sipavičius
2015-09-11 14:06:49 +03:00
parent a4d1f80418
commit c030858562
5 changed files with 421 additions and 11 deletions

View File

@@ -9,10 +9,12 @@
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'newsletter_editor/components/wordpress', 'newsletter_editor/components/wordpress',
'underscore', 'underscore',
'jquery', 'jquery',
], function(App, BaseBlock, WordpressComponent, _, jQuery) { ], function(App, BaseBlock, ButtonBlock, DividerBlock, WordpressComponent, _, jQuery) {
"use strict"; "use strict";
@@ -249,7 +251,7 @@ define([
} }
}, },
showButtonSettings: function(event) { showButtonSettings: function(event) {
var buttonModule = App.module('blocks.button'); var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({ (new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'), model: this.model.get('readMoreButton'),
renderOptions: { renderOptions: {
@@ -260,7 +262,7 @@ define([
})).render(); })).render();
}, },
showDividerSettings: function(event) { showDividerSettings: function(event) {
var dividerModule = App.module('blocks.divider'); var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({ (new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'), model: this.model.get('divider'),
renderOptions: { renderOptions: {

View File

@@ -20,7 +20,9 @@ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/wordpress', 'newsletter_editor/components/wordpress',
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock) { 'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) {
"use strict"; "use strict";
@@ -115,6 +117,12 @@ define([
if (data.posts.length === 0) return; if (data.posts.length === 0) return;
WordpressComponent.getTransformedPosts(data).done(function(posts) {
console.log('Available posts fetched', arguments);
collection.add(posts, { at: index });
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
});
// TODO: Move query logic to new AJAX format // TODO: Move query logic to new AJAX format
//mailpoet_post_wpi('automated_latest_content.php', data, function(response) { //mailpoet_post_wpi('automated_latest_content.php', data, function(response) {
//console.log('Available posts fetched', arguments); //console.log('Available posts fetched', arguments);
@@ -437,7 +445,7 @@ define([
}; };
}, },
showButtonSettings: function(event) { showButtonSettings: function(event) {
var buttonModule = App.module('blocks.button'); var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({ (new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'), model: this.model.get('readMoreButton'),
renderOptions: { renderOptions: {
@@ -448,7 +456,7 @@ define([
})).render(); })).render();
}, },
showDividerSettings: function(event) { showDividerSettings: function(event) {
var dividerModule = App.module('blocks.divider'); var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({ (new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'), model: this.model.get('divider'),
renderOptions: { renderOptions: {

View File

@@ -9,7 +9,8 @@ define([
var postTypesCache, var postTypesCache,
taxonomiesCache = {}, taxonomiesCache = {},
termsCache = {}, termsCache = {},
postsCache = {}; postsCache = {},
transformedPostsCache = {};
Module.getPostTypes = function() { Module.getPostTypes = function() {
if (!postTypesCache) { if (!postTypesCache) {
@@ -65,6 +66,19 @@ define([
return postsCache[key]; return postsCache[key];
}; };
Module.getTransformedPosts = function(options) {
var key = JSON.stringify(options);
if (!transformedPostsCache[key]) {
transformedPostsCache[key] = MailPoet.Ajax.post({
endpoint: 'wordpress',
action: 'getTransformedPosts',
data: options || {},
});
}
return transformedPostsCache[key];
};
App.on('start', function(options) { App.on('start', function(options) {
// Prefetch post types // Prefetch post types
Module.getPostTypes(); Module.getPostTypes();

View File

@@ -0,0 +1,357 @@
<?php
namespace MailPoet\Newsletter;
use \pQuery;
if(!defined('ABSPATH')) exit;
class PostsTransformer {
const MAX_EXCERPT_LENGTH = 60;
/**
* Transforms a list of posts into editor format
*/
static function transform($posts, $args) {
$results = array();
$total_posts = count($posts);
$use_divider = (isset($args['showDivider'])) ? (bool)$args['showDivider'] : false;
$title_list_only = $args['displayType'] === 'titleOnly' && $args['titleFormat'] === 'ul';
foreach ($posts as $index => $post) {
if ($title_list_only) {
$results[] = self::getPostTitle($post, $args);
} else {
$postJSON = self::postToEditorJson($post, $args);
$results = array_merge($results, $postJSON);
if ($use_divider && $index + 1 < $total_posts) {
$results[] = $args['divider'];
}
}
}
if ($title_list_only && !empty($results)) {
$results = array(
array(
'type' => 'text',
'text' => '<ul>' . implode('', $results) . '</ul>',
),
);
}
return $results;
}
private static function postToEditorJson($post, $args) {
if ($args['displayType'] === 'titleOnly') {
$content = '';
} elseif ($args['displayType'] === 'excerpt') {
// get excerpt
if(!empty($post->post_excerpt)) {
$content = $post->post_excerpt;
} else {
// if excerpt is empty then try to find the "more" tag
$excerpts = explode('<!--more-->', $post->post_content);
if (count($excerpts) > 1) {
// <!--more--> separator was present
$content = $excerpts[0];
} else {
// Separator not present, try to shorten long posts
$content = self::postContentToExcerpt($post->post_content, self::MAX_EXCERPT_LENGTH);
}
}
} else {
$content = $post->post_content;
}
if (strlen($post->post_content) < strlen($content)) {
$hideReadMore = true;
} else {
$hideReadMore = false;
}
$content = self::stripShortCodes($content);
// remove wysija nl shortcode
$content = preg_replace('/\<div class="wysija-register">(.*?)\<\/div>/','',$content);
// convert embedded content if necessary
$content = self::convertEmbeddedContent($content);
// convert h4 h5 h6 to h3
$content = preg_replace('/<([\/])?h[456](.*?)>/', '<$1h3$2>', $content);
if ($args['titlePosition'] === 'aboveBlock') {
$content = self::getPostTitle($post, $args) . $content;
}
// convert currency signs
$content = str_replace(array('$', '€', '£', '¥'), array('&#36;', '&euro;', '&pound;', '&#165;'), $content);
// strip useless tags
$tags_not_being_stripped = array('<img>', '<p>','<em>','<span>','<b>','<strong>','<i>','<h1>','<h2>','<h3>','<a>','<ul>','<ol>','<li>','<br>');
$content = strip_tags($content, implode('',$tags_not_being_stripped));
$content = wpautop($content);
if (!$hideReadMore && $args['readMoreType'] === 'link') {
$content .= '<p><a href="' . get_permalink($post->ID) . '" target="_blank">' . stripslashes($args['readMoreText']) . '</a></p>';
}
// Append author and categories above and below contents
foreach (array('above', 'below') as $position) {
$position_field = $position . 'Text';
if ($args['showCategories'] === $position_field || $args['showAuthor'] === $position_field) {
$text = '';
if ($args['showAuthor'] === $position_field) {
$text .= self::getPostAuthor($args['authorPrecededBy'], $post->post_author);
}
if ($args['showCategories'] === $position_field) {
if (!empty($text)) $text .= '<br />';
$text .= self::getPostCategories($args['categoriesPrecededBy'], $post);
}
if (!empty($text)) $text = '<p>' . $text . '</p>';
if ($position === 'above') $content = $text . $content;
else if ($position === 'below') $content .= $text;
}
}
$root = pQuery::parseStr($content);
// Step 1: Hoist image tags to root level, preserving order and inserting
// images before top ancestor
foreach ($root->query('img') as $item) {
$top_ancestor = self::findTopAncestor($item);
$offset = $top_ancestor->index();
if ($item->hasParent('a')) {
$item = $item->parent;
}
$item->changeParent($root, $offset);
}
// Step 2: Perform transformation: turn special tags to their respective
// JSON objects, turn other root children into text blocks
$structure = array();
// Prepend featured image if current post has one
if(in_array($args['displayType'], array('full', 'excerpt')) && has_post_thumbnail($post->ID)) {
$thumbnail_id = get_post_thumbnail_id($post->ID);
// get attachment data (src, width, height)
$image_info = wp_get_attachment_image_src($thumbnail_id, 'single-post-thumbnail');
// get alt text
$alt_text = trim(strip_tags(get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true)));
if(strlen($alt_text) === 0) {
// if the alt text is empty then use the post title
$alt_text = trim(strip_tags($post->post_title));
}
$structure[] = array(
'type' => 'image',
'link' => '',
'src' => $image_info[0],
'alt' => $alt_text,
'padded' => (bool)$args['imagePadded'],
'width' => $image_info[1],
'height' => $image_info[2],
'styles' => array(
'block' => array(
'textAlign' => 'center',
),
),
);
}
foreach ($root->children as $item) {
if ($item->tag === 'img' || $item->tag === 'a' && $item->query('img')) {
$link = '';
$image = $item;
if ($item->tag === 'a') {
$link = $item->getAttribute('href');
$image = $item->children[0];
}
$structure[] = array(
'type' => 'image',
'link' => $link,
'src' => $image->getAttribute('src'),
'alt' => $image->getAttribute('alt'),
'padded' => (bool)$args['imagePadded'],
'width' => $image->getAttribute('width'),
'height' => $image->getAttribute('height'),
'styles' => array(
'block' => array(
'textAlign' => 'center',
),
),
);
} else {
$structure[] = array(
'type' => 'text',
'text' => $item->toString(),
);
}
}
// Step 3: Merge neighboring text blocks into one, remove empty text blocks
$updated_structure = array();
$text_accumulator = '';
foreach ($structure as $item) {
if ($item['type'] === 'text') {
$text_accumulator .= $item['text'];
}
if ($item['type'] !== 'text') {
if (!empty($text_accumulator)) {
$updated_structure[] = array(
'type' => 'text',
'text' => trim($text_accumulator),
);
$text_accumulator = '';
}
$updated_structure[] = $item;
}
}
if (!empty($text_accumulator)) {
$updated_structure[] = array(
'type' => 'text',
'text' => trim($text_accumulator),
);
}
if ($args['titlePosition'] === 'inTextBlock') {
// Attach title to the first text block
$text_block_index = null;
foreach ($updated_structure as $index => $block) {
if ($block['type'] === 'text') {
$text_block_index = $index;
break;
}
}
$title = self::getPostTitle($post, $args);
if ($text_block_index === null) {
$updated_structure[] = array(
'type' => 'text',
'text' => $title,
);
} else {
$updated_structure[$text_block_index]['text'] = $title . $updated_structure[$text_block_index]['text'];
}
}
if (!$hideReadMore && $args['readMoreType'] === 'button') {
$button = $args['readMoreButton'];
$button['url'] = get_permalink($post->ID);
$updated_structure[] = $button;
}
return $updated_structure;
}
private static function findTopAncestor($item) {
while ($item->parent->parent !== null) {
$item = $item->parent;
}
return $item;
}
private static function stripShortCodes($content) {
if(strlen(trim($content)) === 0) {
return '';
}
// remove captions
$content = preg_replace("/\[caption.*?\](.*<\/a>)(.*?)\[\/caption\]/", '$1', $content);
// remove other shortcodes
$content = preg_replace('/\[[^\[\]]*\]/', '', $content);
return $content;
}
private static function convertEmbeddedContent($content = '') {
// remove embedded video and replace with links
$content = preg_replace('#<iframe.*?src=\"(.+?)\".*><\/iframe>#', '<a href="$1">'.__('Click here to view media.').'</a>', $content);
// replace youtube links
$content = preg_replace('#http://www.youtube.com/embed/([a-zA-Z0-9_-]*)#Ui', 'http://www.youtube.com/watch?v=$1', $content);
return $content;
}
private static function getPostAuthor($preceded_by, $author_id) {
if(!empty($author_id)) {
$author_name = get_the_author_meta('display_name', (int)$author_id);
// check if the user specified a label to be displayed before the author's name
if(strlen(trim($preceded_by)) > 0) {
$author_name = stripslashes(trim($preceded_by)).' '.$author_name;
}
return $author_name;
}
return '';
}
private static function getPostCategories($preceded_by, $post) {
$content = '';
// Get categories
$categories = wp_get_post_terms($post->ID, get_object_taxonomies($post->post_type), array('fields' => 'names'));
if(!empty($categories)) {
// check if the user specified a label to be displayed before the author's name
if(strlen(trim($preceded_by)) > 0) {
$content = stripslashes(trim($preceded_by)).' ';
}
$content .= join(', ', $categories);
}
return $content;
}
private static function getPostTitle($post, $args) {
$title = $post->post_title;
if ((bool)$args['titleIsLink']) {
$title = '<a href="' . get_permalink($post->ID) . '">' . $title . '</a>';
}
if (in_array($args['titleFormat'], array('h1', 'h2', 'h3'))) {
$tag = $args['titleFormat'];
} elseif ($args['titleFormat'] === 'ul') {
$tag = 'li';
} else {
$tag = 'h1';
}
$alignment = (in_array($args['titleAlignment'], array('left', 'right', 'center'))) ? $args['titleAlignment'] : 'left';
return '<' . $tag . ' style="text-align: ' . $alignment . '">' . $title . '</' . $tag . '>';
}
/**
* make an excerpt with a certain number of words
* @param type $text
* @param type $num_words
* @param type $more
* @return type
*/
private static function postContentToExcerpt($text, $num_words = 8, $more = ' &hellip;'){
return wp_trim_words($text, $num_words, $more);
}
}

View File

@@ -1,6 +1,8 @@
<?php <?php
namespace MailPoet\Router; namespace MailPoet\Router;
use \MailPoet\Newsletter\PostsTransformer;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Wordpress { class Wordpress {
@@ -43,7 +45,34 @@ class Wordpress {
$parameters['s'] = $args['search']; $parameters['s'] = $args['search'];
} }
if (isset($args['terms']) && is_array($args['terms']) && !empty($args['terms'])) { $parameters['tax_query'] = $this->constructTaxonomiesQuery($args);
wp_send_json(get_posts($parameters));
}
function getTransformedPosts($args) {
$parameters = array(
'posts_per_page' => (isset($args['amount'])) ? (int)$args['amount'] : 10,
'post_type' => (isset($args['contentType'])) ? $args['contentType'] : 'post',
'orderby' => 'date',
'order' => ($args['sortBy'] === 'newest') ? 'DESC' : 'ASC',
);
if (isset($args['posts']) && is_array($args['posts'])) {
$parameters['post__in'] = $args['posts'];
}
$parameters['tax_query'] = $this->constructTaxonomiesQuery($args);
$posts = get_posts($parameters);
wp_send_json(PostsTransformer::transform($posts, $args));
}
private function constructTaxonomiesQuery($args) {
$taxonomies_query = array();
if (isset($args['terms']) && is_array($args['terms'])) {
// Add filtering by tags and categories // Add filtering by tags and categories
$tags = array(); $tags = array();
$categories = array(); $categories = array();
@@ -52,7 +81,6 @@ class Wordpress {
else if ($term['taxonomy'] === 'post_tag') $tags[] = $term['id']; else if ($term['taxonomy'] === 'post_tag') $tags[] = $term['id'];
} }
$taxonomies_query = array();
foreach (array('post_tag' => $tags, 'category' => $categories) as $taxonomy => $terms) { foreach (array('post_tag' => $tags, 'category' => $categories) as $taxonomy => $terms) {
if (!empty($terms)) { if (!empty($terms)) {
$tax = array( $tax = array(
@@ -69,10 +97,11 @@ class Wordpress {
// With exclusion we want to use 'AND', because we want posts that don't have excluded tags/categories // With exclusion we want to use 'AND', because we want posts that don't have excluded tags/categories
// But with inclusion we want to use 'OR', because we want posts that have any of the included tags/categories // But with inclusion we want to use 'OR', because we want posts that have any of the included tags/categories
$taxonomies_query['relation'] = ($args['inclusionType'] === 'exclude') ? 'AND' : 'OR'; $taxonomies_query['relation'] = ($args['inclusionType'] === 'exclude') ? 'AND' : 'OR';
$parameters['tax_query'] = $taxonomies_query;
return $taxonomies_query;
} }
} }
wp_send_json(get_posts($parameters)); return $taxonomies_query;
} }
} }