Plugin Directory

Changeset 1162493


Ignore:
Timestamp:
05/18/2015 07:39:33 AM (11 years ago)
Author:
expresscurate
Message:

new version v2.0.14 with changes:

  • Clone Post allows to copy the original post and re-post it in your blog. Minimal SEO for copy-blogging is done automatically.
  • Miscellaneous bug fixes and improvements.
Location:
expresscurate/trunk
Files:
4 added
19 edited

Legend:

Unmodified
Added
Removed
  • expresscurate/trunk/ExpressCurate.php

    r1147120 r1162493  
    55  Plugin URI: http://www.expresscurate.com/products
    66  Description: ExpressCurate plugin is a content curation tool for WordPress. It enables you to create and publish high quality content within minutes.
    7   Version: 2.0.13
     7  Version: 2.0.14
    88  Author: ExpressCurate
    99  Author URI: http://www.expresscurate.com
  • expresscurate/trunk/ExpressCurate_Actions.php

    r1140784 r1162493  
    5656        $this->cronManager = new ExpressCurate_CronManager();
    5757        $this->feedManager = new ExpressCurate_FeedManager();
     58        $this->socialManager = new ExpressCurate_SocialManager();
    5859
    5960        // register actions
     
    9495
    9596        add_action('admin_notices', array(&$this, 'check_plugins'));
    96         add_action('save_post', array(&$this, 'save_post'));
     97        add_action('save_post', array(&$this, 'save_post'),10,2);
    9798        add_filter('post_updated_messages', array(&$this, 'messages'));
    9899        add_filter('mce_css', array(&$this, 'add_editor_style'));
     
    124125        add_action('wp_ajax_expresscurate_export_api_check_source', array($this->ajaxExportAPI, 'check_source'));
    125126        add_action('wp_ajax_expresscurate_export_api_send_google_key', array($this->ajaxExportAPI, 'send_google_key'));
     127        add_action('wp_ajax_expresscurate_export_api_save_buffer_token', array($this->ajaxExportAPI, 'save_buffer_token'));
    126128
    127129        add_action('wp_ajax_expresscurate_get_article', array($this->contentManager, 'getArticle'));
     
    159161        add_action('wp_ajax_expresscurate_set_cron_permission_status', array($this->cronManager, 'set_permission_status'));
    160162
     163        add_action('wp_ajax_expresscurate_save_active_social_profiles', array($this->socialManager, 'saveActiveProfiles'));
     164        add_action('wp_ajax_expresscurate_save_post_messages', array($this->socialManager, 'savePostMessages'));
    161165
    162166        add_action('wp_ajax_expresscurate_change_tab_event', array(&$this, 'change_tabs'));
     
    217221        register_setting('expresscurate-smartpublish-group', 'expresscurate_hours_interval');
    218222        register_setting('expresscurate-smartpublish-group', 'expresscurate_social_publishing');
     223        register_setting('expresscurate-smartpublish-group', 'expresscurate_social_publishing_profiles');
    219224        register_setting('expresscurate-sitemap-group', 'expresscurate_sitemap_generation_interval');
    220225        register_setting('expresscurate-sitemap-group', 'expresscurate_sitemap_include_new_posts');
     
    328333    href='#' id='expresscurate_open-modal'>
    329334    <span class='expresscurate_button_icon' /></span> Curate Content</a>
     335     <a class='button expresscurate' title='{$title}'
     336     href='#' id='expresscurate_open-modal-clone'>
     337     <span class='expresscurate_button_icon' /></span> Clone Post</a>
    330338    <a class='button expresscurateSocial' title='{$title}'
    331339    href='#' id='expresscurate_socialModal'>
     
    518526//    }
    519527
    520     public function generate_tags($post_id)
     528    public function generate_tags($post)
    521529    {
    522530        $tagsObj = new ExpressCurate_Tags();
     
    529537            $defined_tags = explode(",", $defined_tags);
    530538        }
    531         $the_post = get_post($post_id);
     539        //$the_post = get_post($post_id);
    532540
    533541// get the content of the post
    534         $post_content = $the_post->post_content;
     542        $post_content = $post->post_content;
    535543
    536544        if (strpos($post_content, 'keywordsHighlight') !== false) {
     
    553561            //adding content tag to post tags if not exists
    554562            if (!in_array($content_tag_insert, $post_tags)) {
    555                 wp_set_post_tags($post_id, strtolower($content_tag_insert), true);
     563                wp_set_post_tags($post->ID, strtolower($content_tag_insert), true);
    556564            }
    557565        }
     
    564572                preg_match("/(?!<\w)(?=[^>]*(<|$))" . $defined_tag_insert . "(\W|$)/i", $post_content, $tag_in_content);
    565573                if ((isset($tag_in_content[0]) || strpos($the_post->title, $defined_tag_insert)) && !in_array($defined_tag_insert, $post_tags)) {
    566                     wp_set_post_tags($post_id, $defined_tag_insert, true);
     574                    wp_set_post_tags($post->ID, $defined_tag_insert, true);
    567575                }
    568576            }
     
    593601    }
    594602
    595     public function get_metas($post_id = '', $meta = '_expresscurate_link_%', $type = 'post', $status = 'publish')
     603    public function get_metas($post_id = '', $meta, $type, $status)
    596604    {
    597605        global $wpdb;
     
    657665     * Save the metaboxes for this custom post type
    658666     */
    659     public function save_post($post_id)
    660     {
    661         $post_type = get_post_type($post_id);
     667    public function save_post($post_id,$post=null)
     668    {
     669
     670        //out($post);
     671        //$post_type = get_post_type($post_id);
     672        $post_type = $post->post_type;
    662673
    663674        if ($post_type == 'acf') {
     
    675686        // get the content of the post
    676687        if (get_option('expresscurate_smart_tagging') == "on") {
    677             $post_content = $this->generate_tags($post_id);
     688            $post_content = $this->generate_tags($post);
    678689        } else {
    679             $the_post = get_post($post_id);
    680             $post_content = $the_post->post_content;
     690            //$the_post = get_post($post_id);
     691            $post_content = $post->post_content;
    681692            if (strpos($post_content, 'keywordsHighlight') !== false) {
    682693                $tags_obj = new Expresscurate_Tags();
     
    823834                            // all set, try to download it
    824835                            require_once(ABSPATH . 'wp-admin/includes/image.php');
    825                             $content_manager = new ExpressCurate_HtmlParser($image, true, $image_from);
     836                            $html_parser = new ExpressCurate_HtmlParser($image, true, $image_from);
    826837                            // download
    827                             $image_data = $content_manager->download();
     838                            $image_data = $html_parser->download();
    828839                            if (false === $image_data) {
    829840                                $warning[$post_id]['download_failure'] = "Unable download cover image";
     
    870881
    871882                        } else if ($make_featured) {
    872                             //  var_dump(parse_url($image));
    873                             // $image_filename = basename($image);
    874883                            // create file
    875                             $image_path = parse_url($image);
    876                             $file = $image_path['path'];
     884                            $file = parse_url($image,PHP_URL_PATH);
    877885                            $file = strtok($file, '?');
    878886
     
    905913        preg_match_all('/\sdata-curated-url\s*=\s*(["\'])((?:\\.|(?!\1).)*)\1/i', $post_content, $curated_links);
    906914
    907         $curated_links_meta = $this->get_metas($post_id); //get_post_meta($post_id, '_expresscurate_links');
     915        $curated_links_meta = $this->get_metas($post_id,'_expresscurate_link_%','post','publish'); //get_post_meta($post_id, '_expresscurate_links');
    908916        if (!$curated_links_meta) {
    909917            $curated_links_meta = array();
     
    958966        }
    959967
     968
     969        // check if the post is published
     970        // and publish the social posts too
     971        //$postStatus = get_post_status($post_id);
     972
     973        /*if('publish' == $post->post_status) {
     974            $social = ExpressCurate_SocialManager::getInstance();
     975            $social->publishPostMessages($post_id);
     976        }*/
    960977    }
    961978
     
    14121429        wp_enqueue_script('expresscurate_settings', $pluginUrl . 'js/Settings.js', array('jquery'));
    14131430        wp_enqueue_script('expresscurate_source_collection', $pluginUrl . 'js/sourceCollection.js', array('jquery'));
     1431        wp_enqueue_script('expresscurate_social_post_widget', $pluginUrl . 'js/socialPostWidget.js', array('jquery'));
    14141432        wp_enqueue_script('expresscurate_bookmarks', $pluginUrl . 'js/bookmarks.js', array('jquery', 'masonry'));
    14151433        wp_enqueue_script('expresscurate_feed_settings', $pluginUrl . 'js/feed/feedSettings.js', array('jquery'));
     
    14881506            add_meta_box('dashboard_widget_smartPublishing', 'Smart Publishing Overview', array(&$this, 'smart_publishing_widget'), get_current_screen(), 'side', 'high');
    14891507        }
     1508       
     1509       /* if (get_option('expresscurate_social_publishing', '') == "on" && strlen(get_option('expresscurate_buffer_access_token')) > 2) {
     1510            add_meta_box('dashboard_widget_social_publishing', 'Social Publishing Overview', array(&$this, 'social_publishing_widget'), get_current_screen(), 'side', 'high');
     1511        }*/
    14901512
    14911513        add_meta_box('dashboard_widget_feed', 'Feed', array(&$this, 'feed_widget'), get_current_screen(), 'side', 'high');
     
    15081530            <?php include(sprintf("%s/templates/dashboard/smart_publishing_widget.php", dirname(__FILE__))); ?>
    15091531        </div>
    1510     <?php
     1532        <?php
     1533    }
     1534   
     1535    public function social_publishing_widget()
     1536    {
     1537        ?>
     1538        <div id="expresscurate_social_publishing_widget" class="expresscurate_social_publishing_widget">
     1539            <?php include(sprintf("%s/templates/dashboard/social_publishing_widget.php", dirname(__FILE__))); ?>
     1540        </div>
     1541        <?php
    15111542    }
    15121543
  • expresscurate/trunk/ExpressCurate_AjaxExportAPI.php

    r1137344 r1162493  
    391391        die;
    392392    }
     393   
     394    public function save_buffer_token()
     395    {
     396        $data = $_REQUEST;
     397        if ($data['buffer_token']) {
     398            update_option('expresscurate_buffer_access_token', $data['buffer_token']);
     399            $result = array('status' => true, 'msg' => "Buffer Access Token Accepted.");
     400            wp_redirect('admin.php?page=expresscurate_settings', 301);
     401        } else {
     402            $result = array('status' => false, 'msg' => "Buffer Access Token was not found.");
     403        }
     404        echo json_encode($result);
     405        die;
     406    }
    393407
    394408    public function generate_sitemap()
  • expresscurate/trunk/ExpressCurate_BufferClient.php

    r1140784 r1162493  
    1414    private $accessToken;
    1515   
     16    const POST_FIELD_TEXT = 'text';
     17    const POST_FIELD_PROFILE = 'profile_ids';
     18   
    1619    const BEARER = 'Authorization: Bearer ';
    1720    const ACCESS_TOKEN_URL = 'https://www.expresscurate.com/';
    1821   
     22    const BUFFER_API = 'https://api.bufferapp.com/1';
    1923    const ENDPOINT_GET_PROFILES = '/profiles';
    2024    const ENDPOINT_CREATE_POST = '/updates/create';
     
    2529        '/profiles/:id/updates/sent' => 'get',
    2630           
    27         '/updates/create' => 'post',                                // String text, Array profile_ids, Aool shorten, Bool now, Array media ['link'], ['description'], ['picture']
     31        '/updates/create' => 'post', // String text, Array profile_ids, Aool shorten, Bool now, Array media ['link'], ['description'], ['picture']
    2832    );
    2933       
     
    8084   
    8185    public function getProfiles() {
    82       /*
    83        {
    84     "avatar" : "http://a3.twimg.com/profile_images/1405180232.png",
    85     "formatted_username" : "@skinnyoteam",
    86     "id" : "4eb854340acb04e870000010",
    87     "service" : "twitter",
    88   },
    89     */
    90    
    9186        return $this->go(self::ENDPOINT_GET_PROFILES);
    9287    }
    9388   
    94     public function createPost($post, $time) {
    95     /*
    96     profile_ids
    97 required
    98 array   An array of profile id’s that the status update should be sent to. Invalid profile_id’s will be silently ignored.
    99 text
    100 optional
    101 string  The status update text
    102 shorten
    103 optional
    104 boolean If shorten is false links within the text will not be automatically shortened, otherwise they will.
    105 now
    106 optional
    107 boolean If now is set, this update will be sent immediately to all profiles instead of being added to the buffer.
    108 top
    109 optional
    110 boolean If top is set, this update will be added to the top of the buffer and will become the next update sent.
    111 media
    112 optional
    113 associative array   An associative array of media to be attached to the update, currently accepts link, description, title, picture and thumbnail parameters. For image-based updates, picture and thumbnail parameters are both required.
    114 attachment
    115 optional
    116 boolean In the absence of the media parameter, controls whether a link in the text should automatically populate the media parameter. Defaults to true.
    117 scheduled_at
    118 optional
    119 timestamp or ISO 8601 formatted date-time   A date describing when the update should be posted. Overrides any top or now parameter. When using ISO 8601 format, if no UTC offset is specified, UTC is assumed.
    120     */
    121    
    122         return $this->go(self::ENDPOINT_CREATE_POST);
    123     }
    124    
    125     public function publishPostNow($post) {
    126         // /updates/:id/share
    127     }
    128    
    129     public function publishPostNext($post) {
    130         // /updates/:id/move_to_top
     89    public function createPost($post, $now = false) {
     90        $data = array();
     91        $data[self::POST_FIELD_PROFILE] = array($post[self::POST_FIELD_PROFILE]);
     92        $data[self::POST_FIELD_TEXT] = $post[self::POST_FIELD_TEXT];
     93        $data['top'] = $now;
     94       
     95        return $this->go(self::ENDPOINT_CREATE_POST, $data);
    13196    }
    13297       
    13398    private function go($endpoint = '', $data = '') {
    134         if (in_array($endpoint, array_keys($this->endpoints))) {
    135             $done_endpoint = $endpoint;
     99        // check for access token
     100        $accessToken = $this->getAccessToken();
     101       
     102        if($accessToken == null) {
     103            return null;
     104        }
     105   
     106        // check for the endpoint
     107        if (isset(self::$API_ENDPOINTS[$endpoint])) {
     108            $methodKey = $endpoint;
    136109        } else {
    137110            $ok = false;
    138111           
    139             foreach (array_keys($this->endpoints) as $done_endpoint) {
    140                 if (preg_match('/' . preg_replace('/(\:\w+)/i', '(\w+)', str_replace('/', '\/', $done_endpoint)) . '/i', $endpoint, $match)) {
     112            foreach (array_keys($this->endpoints) as $definedEndpoint) {
     113                if (preg_match('/' . preg_replace('/(\:\w+)/i', '(\w+)', str_replace('/', '\/', $definedEndpoint)) . '/i', $endpoint, $match)) {
    141114                    $ok = true;
     115                    $methodKey = $definedEndpoint;
    142116                    break;
    143117                }
     
    149123        }
    150124       
     125        // fix the data wit access token
    151126        if (!$data || !is_array($data)) {
    152127            $data = array();
    153128        }
    154         $data['access_token'] = $this->getAccessToken();
    155        
    156         //get() or post()?
    157         $method = $this->endpoints[$done_endpoint];
    158         return $this->$method($this->buffer_url . $endpoint . '.json', $data);
     129        $data['access_token'] = $accessToken;
     130        // call
     131        // get() or post()?
     132        $method = self::$API_ENDPOINTS[$methodKey];
     133        return $this->$method(self::BUFFER_API . $endpoint . '.json', $data);
    159134    }
    160135   
     
    167142        }
    168143       
    169         /*
    170         $url = 'https://www.googleapis.com/webmasters/v3/sites/' . urlencode($siteUrl)
    171             . '/sitemaps/' . urlencode($feedPath);
    172         $ch = curl_init($url);
    173         $options = array(
    174             CURLOPT_PUT            => 1,
    175             CURLOPT_RETURNTRANSFER => 1,
    176             CURLOPT_HTTPHEADER     => array(self::BEARER . $accessToken)
    177         );
    178         curl_setopt_array($ch, $options);
    179        
    180         // submit
    181         $response = curl_exec($ch);
    182         */
    183        
    184144        $options = array(CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => false);
    185        
     145
    186146        if ($post) {
    187147            $options += array(
    188                 CURLOPT_POST => $post,
    189                 CURLOPT_POSTFIELDS => $data
     148                CURLOPT_POST => 1,
     149//                CURLOPT_CUSTOMREQUEST => 'POST',
     150                CURLOPT_POSTFIELDS => preg_replace('/%5B[0-9]+%5D/simU', '[]', http_build_query($data))
    190151            );
    191152        } else {
    192153            $url .= '?' . http_build_query($data);
    193154        }
    194        
     155
    195156        $ch = curl_init($url);
    196157        curl_setopt_array($ch, $options);
     158        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    197159        $rs = curl_exec($ch);
    198        
     160
    199161        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    200162        if ($code >= 400) {
    201163            return $this->error($code);
    202164        }
    203        
    204165        return json_decode($rs);
    205166    }
     
    212173        return $this->req($url, $data, true);
    213174    }
     175
     176    private function error($error) {
     177        return (string) array('error' => self::$BUFFER_ERRORS[$error]);
     178    }
    214179   
    215180    private function getAccessToken() {
     
    219184        }
    220185       
    221         // check for refresh token
    222         $refreshToken = get_option('expresscurate_buffer_refresh_token', null);
    223         if($refreshToken) {
    224             // get access token
    225             $this->accessToken = $this->exchangeRefreshTokenWithAccessToken($refreshToken);
    226             return $this->accessToken;
    227         }
     186        // check for access token
     187        $this->accessToken = get_option('expresscurate_buffer_access_token', null);
    228188       
    229         // the refresh token is not set
    230         return null;
    231     }
    232 
    233     private function exchangeRefreshTokenWithAccessToken($refreshToken) {
    234         if($refreshToken) {
    235             $ch = curl_init();
    236             $options = array(
    237                 CURLOPT_URL => self::ACCESS_TOKEN_URL . '/api/connector/buffer/accesstoken',
    238                 CURLOPT_RETURNTRANSFER => true,
    239                 CURLOPT_CUSTOMREQUEST => 'POST',
    240                 CURLOPT_POSTFIELDS => 'refresh_token=' . $refreshToken,
    241                 CURLOPT_ENCODING => "UTF-8",
    242                 CURLOPT_AUTOREFERER => true,
    243                 CURLOPT_CONNECTTIMEOUT => 20,
    244                 CURLOPT_TIMEOUT => 20,
    245                 CURLOPT_SSL_VERIFYPEER => false
    246             );
    247             curl_setopt_array($ch, $options);
    248            
    249             $accessTokenDataJson = curl_exec($ch);
    250             $accessTokenData = json_decode($accessTokenDataJson, true);
    251             $accessToken = $accessTokenData['access_token'];
    252        
    253             return $accessToken;
    254         } else {
    255             return null;
    256         }
     189        // return
     190        return $this->accessToken;
    257191    }
    258192}
  • expresscurate/trunk/ExpressCurate_ContentManager.php

    r1147120 r1162493  
    8080          } else {
    8181            $HtmlParser = new ExpressCurate_HtmlParser($url);
    82             $article = $HtmlParser->getContents();
    83 
     82            if(isset($_REQUEST['cloned']) && $_REQUEST['cloned']==1){
     83              $article = $HtmlParser->getCloneContents();
     84            }else{
     85              $article = $HtmlParser->getContents();
     86            }
    8487            if ($echo == true) {
    8588              echo json_encode($article);
  • expresscurate/trunk/ExpressCurate_HtmlParser.php

    r1147120 r1162493  
    2929    private $dom = null;
    3030    private $article = null;
     31    private $articles = null;
    3132    private $xpath = null;
    3233
     
    167168        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
    168169        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
     170        return $ch;
    169171    }
    170172
     
    222224            return;
    223225        }
    224 
    225 //      if (self::supportsAsynch()) {
    226 //          // setup the single curl
    227 //          $ch = $this->createCURL($this->url);
    228 //          $content = curl_exec($ch);
    229 //          $this->dataHTTPStatus = curl_getinfo($this->asynchHandle, CURLINFO_HTTP_CODE);
    230 //          $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    231 //          curl_close($ch);
    232 //      } else {
    233226        set_time_limit(0);
    234         $header = '';
    235         if ($this->referer) {
    236             $header .= 'Referer: ' . $this->referer . '\r\n';
    237         }
    238         if (!$this->raw) {
    239             $header .= 'Accept: application/xhtml+xml, application/xml, text/html\r\n';
    240             $header .= 'Accept-Charset: UTF-8';
    241         }
    242         $options = array('http' => array(
     227      if (self::supportsAsynch()) {
     228          // setup the single curl
     229          $ch = $this->createCURL($this->url);
     230          $content = curl_exec($ch);
     231          //$this->dataHTTPStatus = curl_getinfo($this->asynchHandle, CURLINFO_HTTP_CODE);
     232          $this->dataHTTPStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
     233          $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
     234          curl_close($ch);
     235      } else {
     236          $header = '';
     237          if ($this->referer) {
     238              $header .= 'Referer: ' . $this->referer . '\r\n';
     239          }
     240          if (!$this->raw) {
     241              $header .= 'Accept: application/xhtml+xml, application/xml, text/html\r\n';
     242              $header .= 'Accept-Charset: UTF-8';
     243          }
     244          $options = array('http' => array(
    243245            'user_agent' => ExpressCurate_Actions::USER_AGENT,
    244246            'follow_location' => 1,
     
    252254        $context = stream_context_create($options);
    253255        $content = @file_get_contents($this->url, false, $context);
    254 
     256        //var_dump($content);
    255257        // $http_response_header gets loaded once file get contents is called, php native stuff
    256258        $this->dataHTTPStatus = $http_response_header;
     
    263265                        $contentTypeData = explode(";", $header);
    264266                        if (count($contentTypeData) == 2) {
    265                             list($contentTypeKey, $contentType) = $contentTypeData;
     267                            list( , $contentType) = $contentTypeData;
    266268                        }
    267269                    }
     
    269271            }
    270272        }
    271 //      }
    272 
     273      }
    273274        // make sure if there is a response at all
    274275        if ($this->isHTTPStatusOK() === false) {
     
    283284
    284285            if ($contentType) {
    285                 list($charset, $encoding) = explode("=", $contentType);
     286                list( , $encoding) = explode("=", $contentType);
    286287                $encoding = strtoupper(trim($encoding));
    287288
     
    328329            $this->removeElementsByTagName('style', $dom);
    329330            $this->removeElementsByTagName('link', $dom);
    330             $this->removeElementsByTagName('nocript', $dom);
     331            $this->removeElementsByTagName('noscript', $dom);
    331332
    332333            // assign
     
    350351            // TODO check the xpath object problem, the final article shall support the same query method
    351352
    352             $article = $this->dom->getElementsByTagName('article')->item(0);
     353            $article = $this->xpath->query('//article')->item(0);
    353354
    354355            if (empty($article)) {
     
    369370
    370371            if (empty($article)) {
    371                 $article = $this->dom->getElementsByTagName('body')->item(0);
     372                $article = $this->xpath->query('//body')->item(0);
    372373            }
    373374
    374375            $this->article = $article;
     376        }
     377    }
     378
     379    private function parseArticles(){
     380        if ($this->articles == null) {
     381            // TODO check the xpath object problem, the final article shall support the same query method
     382
     383            $article = $this->xpath->query('//article');
     384            if ($article->length==0) {
     385                //$article = $this->xpath->query("//*[contains(concat(' ', normalize-space(@class), ' '), 'hentry')]");
     386                $article = $this->xpath->query('//div[contains(concat("\s+", normalize-space(@class), "\s+"), " hentry ")]');
     387            }
     388            if ($article->length==0) {
     389                $article = $this->xpath->query("//*[contains(@itemtype, 'http://schema.org/Article')]");
     390            }
     391            if ($article->length==0) {
     392                $article = $this->xpath->query("//*[contains(@itemtype, 'http://schema.org/TechArticle')]");
     393            }
     394
     395            if ($article->length==0) {
     396                $article = $this->xpath->query("//*[contains(@itemtype, 'http://schema.org/ScholarlyArticle')]");
     397            }
     398
     399            if ($article->length==0) {
     400                $article = $this->xpath->query('//body');
     401            }
     402
     403            $this->articles = $article;
    375404        }
    376405    }
     
    482511            $this->description = $this->getDescription();
    483512
     513            $this->parseDom();
     514            $this->parseArticle();
    484515            // get the contents
    485516            return $this->getElementsByTags();
     
    497528            $this->title = $this->getTitle();
    498529            $this->keywords = $this->getKeywords();
    499 
     530            $this->parseDom();
     531            $this->parseArticles();
    500532            // get the contents
    501533            return $this->cloneElements();
     
    522554        preg_match('/<meta.*?name=("|\')keywords("|\').*?content=("|\')(.*?)("|\')/i', $this->data, $matches);
    523555        if (count($matches) > 4) {
    524             return array_filter(explode(",", trim($matches[4])));
     556            if(strpos(trim($matches[4]),',')){
     557                return array_filter(explode(",", trim($matches[4])));
     558            }
     559            else{
     560                return array_filter(explode(" ", trim($matches[4])));
     561            }
    525562        }
    526563
     
    528565        preg_match('/<meta.*?content=("|\')(.*?)("|\').*?name=("|\')keywords("|\')/i', $this->data, $matches);
    529566        if (count($matches) > 2) {
    530             return array_filter(explode(",", trim($matches[2])));
     567            if(strpos(trim($matches[2]),',')){
     568                return array_filter(explode(",", trim($matches[2])));
     569            }
     570            else{
     571                return array_filter(explode(" ", trim($matches[2])));
     572            }
    531573        }
    532574
     
    558600    private function getElementsByTags()
    559601    {
    560         $this->parseDom();
    561         $this->parseArticle();
    562602
    563603        // TODO make sure this cleanup can be done earlier or later, or maybe shall not affect the base dom with original html at all
     
    715755        return $data;
    716756    }
    717    
     757
     758    private function in_object($value,$object) {
     759        if (is_object($object)) {
     760            foreach($object as $item) {
     761                if ($value->getAttribute('class')==$item->getAttribute('class') && $item->nodeName!='body' ) return true;
     762            }
     763        }
     764        return false;
     765    }
     766
    718767    private function cloneElements()
    719768    {
    720         $this->parseDom();
    721         $this->parseArticle();
    722 
     769        $articleContent = array(
     770            "article_html"=>array(),
     771            "links"=>array(),
     772            "domains"=>array(),
     773            "images"=>array(),
     774            "keywords"=>array(),
     775            "titles"=>array()
     776        );
    723777        // TODO make sure this cleanup can be done earlier or later, or maybe shall not affect the base dom with original html at all
    724778
     779        $main_article = $this->xpath->query("//*[contains(@class, 'hentry')]");
     780        $main_article = ($main_article->length > 0) ? $main_article->item(0) : $this->xpath->query('//body')->item(0);
     781        foreach($this->articles as $article){
     782            $this->getArticlesContent($article,$articleContent);
     783            if(isset($article->parentNode)){
     784                $article->parentNode->removeChild($article);
     785            }
     786        }
     787
     788        if(!$this->in_object($main_article,$this->articles)){
     789            $this->getArticlesContent($main_article,$articleContent);
     790        }
     791        foreach($articleContent as $key => $article_info){
     792            $articleContent[$key] = array_reverse($article_info);
     793        }
     794        $data = array('status' => 'success', 'result' => $articleContent);
     795        return $data;
     796    }
     797
     798    private function getArticlesContent($article,&$articleContent){
     799        $comments = $this->xpath->query(".//comment()",$article);
     800        foreach($comments as $comment){
     801            $comment->parentNode->removeChild($comment);
     802        }
     803        $input = $this->xpath->query(".//node()[name()='iframe' or name()='input' or name()='button' or name='textarea' or name()='form']",$article);
     804        foreach ($input as $inp) {
     805            $inp->parentNode->removeChild($inp);
     806        }
     807        $art = $this->xpath->query('.//article',$article);
     808        if($art->length>0){
     809            foreach($art as $artcl){
     810                $artcl->parentNode->removeChild($artcl);
     811            }
     812        }
     813        $i = 0;
     814        $imgTags = $this->xpath->query(".//node()[name()='img' or contains(@style,'background-image')]", $article);
    725815        $result_images = array();
    726         $imgTags = $this->xpath->query(".//img", $this->article);
    727         $i = 0;
    728816        foreach ($imgTags as $t) {
    729817            $src = $t->getAttribute('src');
     818            $src = !empty($src)? $src : $t->getAttribute('data-src');
     819            $src = !empty($src)? $src : $t->getAttribute('data-src-template');
     820
     821            if(empty($src)) {
     822                $styles = explode(';',$t->getAttribute('style'));
     823                preg_match("/url[\s]*\(([\'\"]*)([^\'\")]*)/i", $styles[0], $output);
     824                if(!empty($output)) $src = $output[2];
     825            }
    730826            if (strlen($src) > 3) {
    731827                if (strpos($src, 'http://') !== false || strpos($src, 'https://') !== false) {
     
    752848            $t->parentNode->removeChild($t);
    753849        }
    754        
     850        $articleContent['images'][] = $result_images;
     851        $title_node = $this->xpath->query('.//node()[name()="h1"]',$article);
     852        $title = "";
     853        if($title_node->length > 0){
     854            $title = $title_node->item(0)->nodeValue;
     855        }
     856        else{
     857            $title_node = $this->xpath->query('.//node()[name()="h2"]',$article);
     858            if($title_node->length > 0){
     859                $title = $title_node->item(0)->nodeValue;
     860            }
     861            else{
     862                $title_node = $this->xpath->query('.//*[contains(@class,"title")]',$article);
     863                $title = $title_node->item(0)->nodeValue;
     864            }
     865        }
     866        $articleContent['titles'][] = trim($title);
     867        if(isset($title_node->item(0)->parentNode)){
     868            $title_node->item(0)->parentNode->removeChild($title_node->item(0));
     869        }
     870        $articleContent['article_html'][] = $this->dom->saveXML($article);
     871        $link = ($this->xpath->query('.//a',$article)->length > 0)?$this->xpath->query('.//a',$article)->item(0)->getAttribute('href'):"";
     872        $link = (strpos($link, '/')==0)? $this->domain.$link : $link;
     873        $articleContent['links'][] = (!empty($link))? $link : "";
     874        $articleContent['domains'][] = (!empty($link))? parse_url($link,PHP_URL_SCHEME) ."://". parse_url($link,PHP_URL_HOST) : "";
    755875        //smart tags
     876        $smart_tags = array();
    756877        $max_count = get_option("expresscurate_max_tags", 3);
    757         $smart_tags = array();
     878
    758879
    759880        $defined_tags = get_option("expresscurate_defined_tags", '');
     
    762883            foreach ($defined_tags as $tag) {
    763884                $tag = trim($tag);
    764                 $count = $this->countMatches($tag);
     885                $count = $this->countMatches($tag,$title,$article->nodeValue);
    765886                if ($count > 0) {
    766887                    $smart_tags[$tag] = $count;
     
    771892        if (count($this->keywords)) {
    772893            foreach ($this->keywords as $key => $keyword) {
    773                 $count = $this->countMatches($key);
     894                $count = $this->countMatches($key,$title,$article->nodeValue);
    774895                if ($count > 0) {
    775896                    $smart_tags[$keyword] = $count;
     
    777898            }
    778899        }
    779 
    780900        if (count($smart_tags) > 0) {
    781901            arsort($smart_tags);
    782902            $smart_tags = array_slice(array_keys(array_reverse($smart_tags)), 0, $max_count);
    783903        }
    784 
    785         $comments = $this->xpath->query(".//comment()",$this->article);
    786         foreach($comments as $cmnt){
    787             $cmnt->parentNode->removeChild($cmnt);
    788         }
    789         $h1Tag = $this->xpath->query(".//h1",$this->article);
    790         foreach ($h1Tag as $h1) {
    791             $h1->parentNode->removeChild($h1);
    792         }
    793         $input = $this->xpath->query(".//node()[name()='iframe' or name()='input' or name()='button' or name='textarea' or name()='form']",$this->article);
    794         foreach ($input as $inp) {
    795             $inp->parentNode->removeChild($inp);
    796         }
    797 
    798 
    799         $articleContent = $this->dom->saveXML($this->article);
    800         $result = array(
    801             'title' => $this->title,
    802             'content'=> $articleContent,
    803             'metas' => array('keywords' => $smart_tags),
    804             'images' => $result_images,
    805             'domain' => $this->domain);
    806         $data = array('status' => 'success', 'result' => $result);
    807         return $data;
    808     }
    809 
    810     private function countMatches($keyword)
     904        $articleContent['keywords'][] = $smart_tags;
     905    }
     906    private function countMatches($keyword,$title=false,$content=false)
    811907    {
    812908        $total_occurrence = 0;
    813909        $tag_in_title = array();
    814910        $tag_in_content = array();
    815         preg_match_all("/(?<!\w)(?=[^>]*(<|$))" . $keyword . "/i", $this->title, $tag_in_title);
    816         preg_match_all("/(?<!\w)(?=[^>]*(<|$))" . $keyword . "/i", $this->data, $tag_in_content);
     911        $title = (!empty($title))? $title: $this->title;
     912        $content = (!empty($content))? $content : $this->data;
     913        preg_match_all("/(?<!\w)(?=[^>]*(<|$))" . $keyword . "/i", $title, $tag_in_title);
     914        preg_match_all("/(?<!\w)(?=[^>]*(<|$))" . $keyword . "/i", $content, $tag_in_content);
    817915        $total_occurrence = count($tag_in_title[0]) + count($tag_in_content[0]);
    818916        return $total_occurrence;
    819917    }
     918
     919
    820920
    821921    private function arrayUnique($array, $preserveKeys = false)
  • expresscurate/trunk/ExpressCurate_Sitemap.php

    r1140784 r1162493  
    136136    }
    137137
    138     public function saveSitemapGoogleStatus(){
     138    public function saveSitemapGoogleStatus() {
    139139        $data = $_REQUEST;
    140140        $status = $data['status'];
    141141        if($status == 'off'){
    142             update_option('expresscurate_google_refresh_token','');
    143         }
    144         update_option('expresscurate_sitemap_submit',$status);
     142            update_option('expresscurate_google_refresh_token', '');
     143        }
     144        update_option('expresscurate_sitemap_submit', $status);
    145145    }
    146146
  • expresscurate/trunk/css/dialog-style-3.9.css

    r1137344 r1162493  
    5151div[aria-describedby="expresscurate_clone_dialog"] .ui-button.ui-dialog-titlebar-close,
    5252div[aria-describedby="expresscurate_dialog"] .ui-button.ui-dialog-titlebar-close{
    53     padding: 0;
     53    padding: 0 !important;
    5454    background: none;
    5555    border: none;
     
    7272
    7373.expresscurate_dialog {
    74     padding: 0;
     74    padding: 0 !important;
    7575    border:none;
    7676}
  • expresscurate/trunk/css/expresscurate.css

    r1147120 r1162493  
    653653        overflow: hidden;
    654654        max-height: 120px;
    655         overflow: hidden;
    656655        display: block;
    657656    }
     
    18851884.expresscurate_dialog .footer .labels {
    18861885    list-style: none;
     1886    margin: 0;
    18871887}
    18881888
     
    24052405    display: none;
    24062406}
     2407.expresscurate_dialog #cloneControlsWrap{
     2408    margin-top: 10px;
     2409}
     2410.expresscurate_dialog #articlesSliderWrap{
     2411    display: inline-block;
     2412}
     2413    .expresscurate_dialog .articlesSlider{
     2414        padding: 0;
     2415        margin: 0;
     2416        width: auto;
     2417        display: inline-block;
     2418    }
     2419        .expresscurate_dialog .articleDescription{
     2420            line-height: 40px;
     2421            display: inline-block;
     2422            vertical-align: top;
     2423        }
     2424        .expresscurate_dialog .articlesSlider li{
     2425            display: inline-block;
     2426            width: 40px;
     2427            height: 40px;
     2428            margin: 0;
     2429            vertical-align: top;
     2430        }
     2431        .expresscurate_dialog .articlesSlider li.prevArticle,
     2432        .expresscurate_dialog .articlesSlider li.nextArticle{
     2433            background: url("../images/expresscurate_icons.svg") no-repeat;
     2434            background-size: 30px auto;
     2435            cursor: pointer;
     2436        }
     2437        .expresscurate_dialog .articlesSlider li.currentArticle{
     2438            line-height: 40px;
     2439            text-align: center;
     2440            width: 30px;
     2441        }
     2442        .expresscurate_dialog .articlesSlider li.prevArticle{
     2443            background-position: center -355px;
     2444        }
     2445        .expresscurate_dialog .articlesSlider li.nextArticle{
     2446            background-position: center -475px;
     2447        }
     2448        .expresscurate_dialog .articlesSlider li.prevArticle.active{
     2449            background-position: center -415px;
     2450        }
     2451        .expresscurate_dialog .articlesSlider li.nextArticle.active{
     2452            background-position: center -535px;
     2453        }
     2454
    24072455/*Advanced SEO*/
    24082456#expresscurate_advanced_seo .inside{
     
    28732921    display: inline-block;
    28742922    border: 2px solid #1cbb9f;
    2875     height: 24px;
    2876     line-height: 24px;
     2923    height: 22px;
     2924    line-height: 22px;
    28772925    padding: 0 10px;
    28782926    color: #1cbb9f;
     
    48274875.expresscurate_smartPublishBlock,
    48284876.expresscurate_feedBlock,
    4829 .expresscurate_bookmarksBlock{
     4877.expresscurate_bookmarksBlock,
     4878.expresscurate_socialPublishBlock{
    48304879    background-color: #ffffff;
    48314880    padding: 0;
     
    49394988    }
    49404989}
    4941 
     4990/*social Tweet widget*/
     4991.expresscurate_social_post_widget{
     4992    padding-top: 10px;
     4993}
     4994    .expresscurate_tweetBlock{
     4995        border-radius: 2px;
     4996        padding: 0 10px;
     4997        box-sizing: border-box;
     4998        -moz-box-sizing: border-box;
     4999        -webkit-box-sizing: border-box;
     5000        border: solid 1px #E6E6E6;
     5001        margin: 10px 0;
     5002    }
     5003    .expresscurate_social_post_widget .mainControls{
     5004        margin: 0 0 10px 0;
     5005    }
     5006            .expresscurate_tweetBlock .topControls,
     5007            .expresscurate_tweetBlock .bottomControls{
     5008                margin: 5px 0;
     5009                padding: 0;
     5010            }
     5011                .expresscurate_tweetBlock .topControls li,
     5012                .expresscurate_tweetBlock .bottomControls li{
     5013                    display: inline-block;
     5014                    height: 25px;
     5015                    margin: 0;
     5016                }
     5017                .expresscurate_tweetBlock .topControls li.close:hover{
     5018                    background: url("../images/feed_icons.svg") no-repeat -48px -21px;
     5019                    background-size: auto 68px;
     5020                }
     5021                .expresscurate_tweetBlock .topControls li.close{
     5022                    background: url("../images/feed_icons.svg") no-repeat -48px 6px;
     5023                    background-size: auto 68px;
     5024                    width: 25px;
     5025                    cursor: pointer;
     5026                }
     5027
     5028            .expresscurate_social_post_content{
     5029                display: block;
     5030                resize: none;
     5031                width: 100%;
     5032                height: 90px;
     5033                background-color: #f6f6f6;
     5034                border-radius: 2px;
     5035                -webkit-border-radius: 2px;
     5036                -moz-border-radius: 2px;
     5037                box-shadow: none;
     5038                -moz-box-shadow: none;
     5039                -webkit-box-shadow: none;
     5040                font-size: 13px;
     5041                padding: 0 10px;
     5042                border: solid 1px #E6E6E6;
     5043            }
     5044            .expresscurate_social_widget_buttons{
     5045                display: inline-block;
     5046                border: 2px solid #1cbb9f;
     5047                height: 22px;
     5048                line-height: 22px;
     5049                padding: 0 10px;
     5050                color: #1cbb9f;
     5051                border-radius: 3px;
     5052                text-decoration: none;
     5053                transition: all .3s ease-out;
     5054                -moz-transition: all .3s ease-out;
     5055                -webkit-transition: all .3s ease-out;
     5056                margin: 0;
     5057                cursor: pointer;
     5058            }
     5059                .expresscurate_social_widget_buttons:hover {
     5060                     background-color: #1cbb9f;
     5061                     color: #ffffff;
     5062                 }
     5063/*dashboard social widget*/
     5064    .expresscurate_social_box{
     5065        line-height: 30px;
     5066        font-family: OpenSans-Semibold, Verdana, Geneva, sans-serif;
     5067        font-size: 15px;
     5068        color: #fff;
     5069        text-align: left;
     5070        box-sizing: border-box;
     5071        -moz-box-sizing: border-box;
     5072        -webkit-box-sizing: border-box;
     5073        padding-left: 40px;
     5074        background: url("../images/socials_box.svg") no-repeat;
     5075        background-size: auto 100px;
     5076        max-height: 30px;
     5077        position: relative;
     5078    }
     5079    .expresscurate_social_box_Twitter{
     5080        background-color: rgba(0,172,273,1);
     5081        background-position: 0 -72px;
     5082    }
     5083    .expresscurate_social_box_Twitter:hover{
     5084        background-color: rgba(0,172,273,0.8);
     5085    }
     5086    .expresscurate_social_box_Facebook{
     5087        background-color: rgba(59,89,152,1);
     5088        background-position: 0 -2px;
     5089    }
     5090    .expresscurate_social_box_Facebook:hover{
     5091        background-color: rgba(59,89,152,0.8);
     5092    }
     5093        .expresscurate_social_box .dailySuggestions{
     5094            display: inline-block;
     5095            min-width: 22px;
     5096            height: 18px;
     5097            border-radius: 3px;
     5098            background-color: rgba(0,0,0,0.2);
     5099            color: #fff;
     5100            font-family: OpenSans-Regular, Verdana, Geneva, sans-serif;
     5101            font-size: 13px;
     5102            text-align: center;
     5103            line-height: 18px;
     5104            margin: 6px 8px 0 0;
     5105            float: right;
     5106            padding: 0 3px;
     5107            position: absolute;
     5108            right: 0;
     5109        }
     5110        .expresscurate_social_box .text{
     5111            display: inline-block;
     5112            max-height: 30px;
     5113            max-width: 115px;
     5114            overflow: hidden;
     5115            text-overflow: ellipsis;
     5116        }
     5117            .expresscurate_social_box .dailySuggestions .tooltip{
     5118                visibility: hidden;
     5119                opacity: 0;
     5120                transition: visibility 0s linear 0.3s,opacity 0.3s linear;
     5121                position: absolute;
     5122                font-size: 12px;
     5123                font-family: OpenSans-Regular, Verdana, Geneva, sans-serif;
     5124                line-height: 14px;
     5125                background: #161616;
     5126                color: #ffffff;
     5127                -moz-border-radius: 3px;
     5128                -webkit-border-radius: 3px;
     5129                border-radius: 3px;
     5130                padding: 7px;
     5131                width: 100px;
     5132                height: auto;
     5133                text-align: center;
     5134                z-index: 50;
     5135                right: -44px;
     5136                top: 25px;
     5137            }
     5138                .expresscurate_social_box .dailySuggestions .tooltip:before{
     5139                    content: '';
     5140                    display: block;
     5141                    width: 0;
     5142                    height: 0;
     5143                    position: absolute;
     5144                    border-left: 5px solid transparent;
     5145                    border-right: 5px solid transparent;
     5146                    border-bottom: 5px solid #161616;
     5147                    top: -5px;
     5148                    left: 52px;
     5149                }
     5150                .expresscurate_social_box .dailySuggestions:hover .tooltip{
     5151                    visibility:visible;
     5152                    opacity:1;
     5153                    transition-delay:0.3s;
     5154                }
     5155/**/
    49425156.expresscurate_news_container{
    49435157    margin-top: 30px;
  • expresscurate/trunk/js/Dialog.js

    r1147120 r1162493  
    66        html,
    77        alignImg,
    8         imgSize;
     8        imgSize,
     9        clonedArticles = [],
     10        $articlePrev,
     11        $articleNext,
     12        $dialog;
    913
    1014    function sendWPEditor(html, insertedTags) {
     
    6165                    if (validImgCount === 1) {
    6266                        $('.content .img').removeClass("noimage").css('background-image', $('ul#curated_images li').first().css('background-image'));
    63                         $('#expresscurate_dialog').find('div.error.dialogImgError').remove();
     67                        $dialog.find('div.error.dialogImgError').remove();
    6468                    }
    6569                }
     
    235239    }
    236240
    237     function clearExpresscurateForm() {
    238         var $dialog = $('#expresscurate_dialog'),
     241    function clearExpresscurateForm(articleSlider) {
     242        var $dialog = $("#expresscurate_dialog"),
    239243            $imageCounter = $dialog.find('.imageCount');
    240244        $dialog.find('div.error').remove();
    241245        $dialog.find('div.updated').remove();
    242         $dialog.find('ul').html('');
     246        $dialog.find('ul').not($('.articlesSlider')).html('');
    243247        $dialog.find('#curated_title').val('');
     248        if (articleSlider) {
     249            $dialog.find('#articlesSliderWrap').addClass('expresscurate_displayNone');
     250        }
    244251        $imageCounter.text('0/0');
    245252        $('.content .img').attr('style', '').addClass("noimage");
     
    319326
    320327    function submitExpresscurateForm(clone) {
    321         var $dialog = $('#expresscurate_dialog');
     328        $dialog = $('#expresscurate_dialog');
    322329        //remove autoComplete
    323330        $dialog.find('.autoComplete').remove();
     
    328335        var errorHTML = '',
    329336            notifHTML = '',
     337            cloned = clone ? "&cloned=1" : "",
    330338            $url = clone ? $('#expresscurate_post_form').find('#expresscurate_clone_source') : $('#expresscurate_post_form').find('#expresscurate_source');
    331339        $.ajax({
     
    342350        $.ajax({
    343351            type: 'POST',
    344             url: 'admin-ajax.php?action=expresscurate_get_article',
     352            url: 'admin-ajax.php?action=expresscurate_get_article' + cloned,
    345353            data: $url.serialize()
    346354        }).done(function (res) {
     
    353361                    } else if (data.status === 'success') {
    354362                        clearExpresscurateForm();
    355                         if (data.result.title && data.result.title.length > 0) {
    356                             $("#curated_title").val(data.result.title);
    357                         }
    358                         if (data.result.images.length > 0) {
    359                             errorHTML = exportAPICheckImages(data.result.images);
     363                        if (clone) {
     364                            var html = "",
     365                                postCount = data.result.article_html.length,
     366                                $sliderWrap = $('#articlesSliderWrap'),
     367                                $label = $sliderWrap.find('.articleDescription span'),
     368                                $currentArticle = $sliderWrap.find('.currentArticle');
     369                            if (postCount > 1) {
     370
     371                                $sliderWrap.removeClass('expresscurate_displayNone');
     372                                $label.text(postCount);
     373                                $currentArticle.text('1');
     374                                $articlePrev.removeClass('active');
     375                                $articleNext.addClass('active');
     376                            } else {
     377                                $sliderWrap.addClass('expresscurate_displayNone');
     378                            }
     379                            tinyMCE.get('expresscurate_dialog_content_clone_editor').execCommand('mceInsertContent', false, data.result.article_html[0]);
     380                            clonedArticles = data.result;
     381
     382                            var title = data.result.titles[0];
     383                            if (title && title.length > 0) {
     384                                $("#curated_title").val(title);
     385                            }
     386                            if (data.result.images[0].length > 0) {
     387                                errorHTML = exportAPICheckImages(data.result.images[0]);
     388                            } else {
     389                                $("#expresscurate_loading").fadeOut('fast');
     390                            }
     391                            keywords = data.result.keywords[0];
     392                            if (keywords && keywords.length > 0) {
     393                                displayCuratedTags(keywords);
     394                            }
    360395                        } else {
    361                             $("#expresscurate_loading").fadeOut('fast');
    362                         }
    363                         keywords = data.result.metas.keywords;
    364                         if (data.result.metas.keywords && data.result.metas.keywords.length > 0) {
    365                             displayCuratedTags(data.result.metas.keywords);
    366                         }
    367                         if (clone) {
    368                             if (data.result.paragraphs.length > 0) {
    369                                 tinyMCE.get('expresscurate_dialog_content_clone_editor').execCommand('mceInsertContent', false, data.result.content);
     396                            if (data.result.title && data.result.title.length > 0) {
     397                                $("#curated_title").val(data.result.title);
    370398                            }
    371                             ExpressCurateUtils.track('/post/content-dialog/clonepage');
    372                         } else {
     399                            if (data.result.images.length > 0) {
     400                                errorHTML = exportAPICheckImages(data.result.images);
     401                            } else {
     402                                $("#expresscurate_loading").fadeOut('fast');
     403                            }
     404                            keywords = data.result.metas.keywords;
     405                            if (data.result.metas.keywords && data.result.metas.keywords.length > 0) {
     406                                displayCuratedTags(data.result.metas.keywords);
     407                            }
    373408                            $(".controls").show();
    374409                            displaySpecials(data.result);
     
    394429    function insertContent(clone, addAndContinue) {
    395430        var ed = tinyMCE.activeEditor,
    396             $dialog = $('#expresscurate_dialog'),
    397431            highlightedElems = $(ed.getBody()).find('span.expresscurate_keywordsHighlight');
    398432        if (clone) {
     
    407441        }
    408442        var insertedTagsTextarea = "",
    409             sourceVal = $('#expresscurate_source').val(),
    410             postTag = $("#tax-input-post_tag");
     443            sourceVal = (clone) ? $('#expresscurate_clone_source').val() : $('#expresscurate_source').val(),
     444            postTag = $("#tax-input-post_tag"),
     445            domain;
    411446        insertedTagsTextarea = postTag.val();
    412447        $('#curated_tags').find('li').each(function () {
     
    433468        if (html.length > 0) {
    434469            if (sourceVal.length > 0) {
    435                 var domain = sourceVal;
     470                domain = sourceVal;
    436471                if (domain.indexOf('http://') === -1 && domain.indexOf('https://') === -1) {
    437472                    domain = 'http://' + domain;
     
    467502                    $copyControlWrap.removeClass('expresscurate_displayNone');
    468503                    $copyCheckVal.val('on');
    469                     $canonicalURL.attr('value', domain).attr('readonly', true);
     504                    $canonicalURL.val(domain).attr('readonly', true);
    470505                    $noFollow.add($noIndex).attr('checked', false).attr('disabled', true);
    471506                    $noFollowVal.add($noIndexVal).val('off');
     
    487522                clearExpresscurateForm();
    488523            }
    489 
    490524        } else {
    491525            return false;
     
    493527    }
    494528
     529    function switchArticles(next) {
     530        var $currentArticleLi = $('.currentArticle'),
     531            currentArticle = parseInt($currentArticleLi.text()),
     532            newArticle,
     533            articlesCount = clonedArticles.titles.length;
     534        if (next) {
     535            newArticle = currentArticle + 1;
     536            $currentArticleLi.text(newArticle);
     537            if (newArticle == articlesCount) {
     538                $articleNext.removeClass('active');
     539                $articlePrev.addClass('active');
     540            } else if (newArticle == 2) {
     541                $articlePrev.addClass('active');
     542            }
     543        } else {
     544            newArticle = currentArticle - 1;
     545            $currentArticleLi.text(newArticle);
     546            if (newArticle == 1) {
     547                $articlePrev.removeClass('active');
     548                $articleNext.addClass('active');
     549            } else if (newArticle == 2) {
     550                $articlePrev.addClass('active');
     551            }
     552            if (newArticle < articlesCount) {
     553                $articleNext.addClass('active');
     554            }
     555        }
     556        var editor = tinyMCE.get('expresscurate_dialog_content_clone_editor'),
     557            currentNumber = newArticle - 1,
     558            title = clonedArticles.titles[currentNumber],
     559            images = clonedArticles.images[currentNumber],
     560            keywords = clonedArticles.keywords[currentNumber];
     561        clearExpresscurateForm(false);
     562        editor.setContent('');
     563        editor.execCommand('mceInsertContent', false, clonedArticles.article_html[currentNumber]);
     564
     565        if (title && title.length > 0) {
     566            $("#curated_title").val(title);
     567        }
     568        if (images.length > 0) {
     569            exportAPICheckImages(images);
     570        } else {
     571            $("#expresscurate_loading").fadeOut('fast');
     572        }
     573        if (keywords && keywords.length > 0) {
     574            displayCuratedTags(keywords);
     575        }
     576    }
     577
    495578    function setupDialog() {
    496         var $dialog = $('#expresscurate_dialog');
     579        $dialog = $('#expresscurate_dialog');
     580        $articlePrev = $dialog.find('.prevArticle');
     581        $articleNext = $dialog.find('.nextArticle');
    497582        buttonsStatus();
    498583        $dialog.on('click', '.tcurated_text', function () {
     
    518603            if (!$(e.target).is('.autoComplete li')) {
    519604                $dialog.find('.autoComplete').remove();
     605            }
     606        });
     607        /*cloned articles slider*/
     608        $articlePrev.on('click', function () {
     609            var $this = $(this);
     610            if ($this.hasClass('active')) {
     611                switchArticles(false);
     612            }
     613        });
     614        $articleNext.on('click', function () {
     615            var $this = $(this);
     616            if ($this.hasClass('active')) {
     617                switchArticles(true);
    520618            }
    521619        });
     
    751849            $clone = $dialog.find('#expresscurate_clone'),
    752850            $insert = $dialog.find('#curateControlsWrap'),
    753             $insertClone = $dialog.find('#expresscurate_cloneInsert'),
     851            $insertClone = $dialog.find('#cloneControlsWrap'),
    754852            $contenttextarea = $dialog.find('#expresscurate_dialog_content_editor_container'),
    755853            $cloneContentTextarea = $dialog.find('#expresscurate_dialog_content_clone_editor_container'),
  • expresscurate/trunk/js/Settings.js

    r1147120 r1162493  
    6060            showHideOptions($('#smartPublishingWrap'), $(this));
    6161        });
     62        /*social publishing*/
     63        $('#expresscurate_social_publishing').on('change', function () {
     64            showHideOptions($('.socialPublishingWrap'), $(this));
     65        });
     66        $('html').on('change', '.expresscurate_social_publishing_profile', function () {
     67            var $this = $(this),
     68                id = $this.data('id'),
     69                $profilesWrap = $('#expresscurate_social_publishing_profiles'),
     70                status,
     71                profiles = {};
     72            if ($profilesWrap.val().length > 4) {
     73                profiles = $.parseJSON(decodeURIComponent($profilesWrap.val()));
     74            }
     75            status = ($this.is(':checked')) ? 'on' : 'off';
     76            profiles[id] = status;
     77            $profilesWrap.val(encodeURIComponent(JSON.stringify(profiles)));
     78        });
    6279
    6380        /*feed*/
     
    109126        $submitSitemap.on('click', function () {
    110127            $('.expresscurate_Error').remove();
    111             var $this=$(this),
     128            var $this = $(this),
    112129                $loading = $this.find('.loading');
    113130            $this.addClass('hideText');
     
    124141                    $submitSitemap.addClass('generated');
    125142                    message = data.message;
    126                     className='expresscurate_SettingsSuccess';
    127                 } else{
    128                     className='expresscurate_SettingsError';
     143                    className = 'expresscurate_SettingsSuccess';
     144                } else {
     145                    className = 'expresscurate_SettingsError';
    129146                    if (data.status === 1) {
    130147                        message = data.message;
     
    135152                }
    136153            }).always(function () {
    137                 $submitSitemap.after('<p class="expresscurate_Error '+className+'">' + message + '</p>');
     154                $submitSitemap.after('<p class="expresscurate_Error ' + className + '">' + message + '</p>');
    138155                $loading.removeClass('expresscurate_startRotate');
    139156                $this.removeClass('hideText');
  • expresscurate/trunk/readme.txt

    r1148038 r1162493  
    44Donate link: https://bit.ly/expresscuratedonate
    55Requires at least: 3.9
    6 Tested up to: 4.2.1
    7 Stable tag: 2.0.13
     6Tested up to: 4.2.2
     7Stable tag: 2.0.14
    88License: GPLv3 or later
    99License URI: http://www.gnu.org/licenses/gpl.html
     
    8181
    8282= How To Get Started =
    83 [Download ExpressCurate plugin](http://downloads.wordpress.org/plugin/expresscurate.2.0.13.zip "Your favorite content marketing tools") for WordPress. 
     83[Download ExpressCurate plugin](http://downloads.wordpress.org/plugin/expresscurate.2.0.14.zip "Your favorite content marketing tools") for WordPress. 
    8484You can also [download](http://www.expresscurate.com/p/products/wordpress-theme "Your favorite WordPress Theme") a **free** [ExpressCurate WordPress theme](http://www.expresscurate.com/p/products/wordpress-theme "Your favorite WordPress Theme"). It will give your curated content a modern online news look.
    8585
     
    131131== Changelog ==
    132132
     133= 2.0.14 =
     134* Clone Post allows to copy the original post and re-post it in your blog. Minimal SEO for copy-blogging is done automatically.
     135* Miscellaneous bug fixes and improvements.
     136
    133137= 2.0.13 =
    134138* Wordpress 4.2 supported now.
  • expresscurate/trunk/templates/advanced_seo_widget.php

    r1140784 r1162493  
    2424
    2525        <div id="expresscurate_advancedSEO_widget">
    26             <!--<div id="expresscurate_ClonePostWrap" class="<?php /*if(!$postCopy) {echo 'expresscurate_displayNone';} */?>">
     26            <div id="expresscurate_ClonePostWrap" class="<?php if(!$postCopy) {echo 'expresscurate_displayNone';} ?>">
    2727                <div class="info">
    28                     <label for="expresscurate_advanced_seo_canonical_url" class="label">Content cloned</label>
     28                    <label for="expresscurate_advanced_seo_post_copy" class="label">Content cloned</label>
    2929                </div>
    3030                <div class="value">
    3131                    <input class="expresscurate_displayNone" id="expresscurate_advanced_seo_post_copy"
    32                            type='checkbox' <?php /*if(!$postCopy) {
     32                           type='checkbox' <?php if(!$postCopy) {
    3333                        echo 'disabled="disabled"';
    34                     } */?>
     34                    } ?>
    3535                           name='expresscurate_advanced_seo_post_copy'
    36                         <?php /*echo ($postCopy) ? 'checked' : '' */?>>
     36                        <?php echo ($postCopy) ? 'checked' : '' ?>>
    3737                    <label class="expresscurate_preventTextSelection" for="expresscurate_advanced_seo_post_copy"></label>
    3838                    <input type="hidden" id="expresscurate_advanced_seo_post_copy_value"
    3939                           name="expresscurate_advanced_seo_post_copy_value"
    40                            value="<?php /*echo ($postCopy) ? "on" : "off"; */?>">
     40                           value="<?php echo ($postCopy) ? "on" : "off"; ?>">
    4141                </div>
    42             </div>-->
     42            </div>
    4343            <div class="info">
    4444                <label for="expresscurate_advanced_seo_title" class="label">SEO Title</label>
  • expresscurate/trunk/templates/dashboard.php

    r1137344 r1162493  
    2020<div class="expresscurate_blocks expresscurate_Styles wrap">
    2121    <div class="expresscurate_headBorderBottom expresscurate_OpenSansRegular">
    22         <a href="admin.php?page=expresscurate_support" class="expresscurate_writeUs">Suggestions? <span>Submit here!</span></a>
     22        <a href="admin.php?page=expresscurate_support" class="expresscurate_writeUs">Suggestions?
     23            <span>Submit here!</span></a>
    2324
    2425        <h2>ExpressCurate</h2>
     
    5354                                            <label class="label">Smart Publishing Overview</label>';
    5455                        $this->smart_publishing_widget();
     56                        echo '</div>';
     57                    }
     58                endif;
     59                if ($ordered_item == "socialPublish"):
     60                    if (get_option('expresscurate_social_publishing', '') == "on" && strlen(get_option('expresscurate_buffer_access_token')) > 2) {
     61                        echo '<div id="socialPublish" class="expresscurate_socialPublishBlock expresscurate_masonryItem">
     62                                            <label class="label">Social Publishing Overview</label>';
     63                        $this->social_publishing_widget();
    5564                        echo '</div>';
    5665                    }
     
    120129            <?php } ?>
    121130
     131            <?php
     132            if (get_option('expresscurate_social_publishing', '') == "on" && strlen(get_option('expresscurate_buffer_access_token')) > 2) { ?>
     133                <div id="socialPublish" class="expresscurate_socialPublishBlock expresscurate_masonryItem">
     134                    <label class="label">Social Publishing Overview</label>
     135                    <?php $this->social_publishing_widget(); ?>
     136                </div>
     137            <?php } ?>
     138
    122139            <div id="feedWidget" class="expresscurate_feedBlock expresscurate_masonryItem">
    123140                <label class="label">Feed</label>
  • expresscurate/trunk/templates/dialog.php

    r1140784 r1162493  
    122122                        onclick="return false;"><?php echo __('Add and continue', ExpressCurate_Actions::PLUGIN_FOLDER) ?></button>
    123123            </div>
    124             <button class="curate right expresscurate_displayNone" id="expresscurate_cloneInsert"
    125                     onclick="return false;"><?php echo __('Clone Post', ExpressCurate_Actions::PLUGIN_FOLDER) ?></button>
     124            <div id="cloneControlsWrap">
     125                <div class="expresscurate_displayNone expresscurate_preventTextSelection" id="articlesSliderWrap">
     126                    <label class="articleDescription">Found <span></span> articles.</label>
     127                    <ul class="articlesSlider">
     128                        <li class="prevArticle"></li>
     129                        <li class="currentArticle"></li>
     130                        <li class="nextArticle"></li>
     131                    </ul>
     132                </div>
     133                <button class="curate right" id="expresscurate_cloneInsert"
     134                        onclick="return false;"><?php echo __('Clone Post', ExpressCurate_Actions::PLUGIN_FOLDER) ?></button>
     135            </div>
    126136            <div class="clear"></div>
    127137        </div>
  • expresscurate/trunk/templates/settings.php

    r1140784 r1162493  
    7979                        ?>" name="expresscurate_curated_text" size="50"/>
    8080                    </li>
    81                     <li>                       
    82                         <p class="title">Open Original Article Link in a New Window/Tab<span scope="row" class="description  ">
     81                    <li>
     82                        <p class="title">Open Original Article Link in a New Window/Tab<span scope="row"
     83                                                                                             class="description  ">
    8384            Select "Yes" if you want the original article link to be opened in a New Window/Tab. Select "No" if you want the link to be opened in the Same Window/Tab (default behavior).
    8485          </span></p>
     
    9192                        ?> />
    9293                        <label class="controls checkboxLabel" for="expresscurate_curated_link_target"></label>
    93                        
     94
    9495                    </li>
    9596                    <li>
     
    99100                        </label>
    100101                        <input type="text" id="expresscurate_max_tags" class="controls" value="<?php
    101                         if (get_option('expresscurate_max_tags') == false) {
     102                        if (get_option('expresscurate_max_tags') !== false) {
    102103                            echo get_option('expresscurate_max_tags');
    103104                        } else {
     
    111112                            <br/><span>&nbsp;&nbsp; The default value is 5</span></label>
    112113                        <input type="text" id="expresscurate_autosummary" class="controls" value="<?php
    113                         if (get_option('expresscurate_autosummary') == false) {
     114                        if (get_option('expresscurate_autosummary') !== false) {
    114115                            echo get_option('expresscurate_autosummary');
    115116                        } else {
     
    226227    <div id="tab-2" class="tab-content">
    227228        <div>
    228             <form class="expresscurate_marginTop20" method="post" action="options.php">
     229            <form class="expresscurate_marginTop20" id="smartPublishingSettingsTab" method="post" action="options.php">
    229230                <?php @settings_fields('expresscurate-smartpublish-group'); ?>
    230231                <?php @do_settings_fields('expresscurate-smartpublish-group'); ?>
     
    300301                            </select>
    301302                    </li>
    302                     <!--<li>
     303             <!--       <li>
    303304                        <p class="title">Social publishing </p>
    304305                        <input class="expresscurate_displayNone" type="checkbox" id="expresscurate_social_publishing"
     
    309310                        */?> />
    310311                        <label class="controls checkboxLabel" for="expresscurate_social_publishing"></label>
    311                        
    312                         <?php /*
    313                              $blogName = urlencode(urlencode(get_bloginfo('url')));
     312
     313                        <?php
     314/*                        $blogName = urlencode(urlencode(get_bloginfo('url')));
    314315                        */?>
    315                         <a class="getApiKey  <?php /*if (strlen(get_option('expresscurate_buffer_refresh_token')) > 2) {echo 'expresscurate_displayNone';}*/?>" href="https://www.expresscurate.com/api/connector/buffer/refreshtoken/<?/*=$blogName*/?>">Authorize access to Buffer</a>
    316                     </li>-->
     316                        <a class="getApiKey  <?php /*if (strlen(get_option('expresscurate_buffer_access_token')) > 2) {
     317                            echo 'expresscurate_displayNone';
     318                        } */?>"
     319                           href="https://www.expresscurate.com/api/connector/buffer/accesstoken/<?php /*echo $blogName */?>">Authorize
     320                            access to Buffer</a>
     321                    </li>
     322                    <div
     323                        class="socialPublishingWrap <?php /*if (get_option('expresscurate_social_publishing', '') !== "on") {
     324                            echo 'expresscurate_displayNone';
     325                        } */?> ">
     326
     327                        <?php
     328/*                        $buffer = new ExpressCurate_BufferClient();
     329                        $profiles = $buffer->getProfiles();
     330                        if (!empty($profiles)) {
     331                            $profilesStatus = array();
     332                            if (get_option('expresscurate_social_publishing_profiles', '')) {
     333                                $profilesStatus = json_decode(stripslashes(urldecode(get_option('expresscurate_social_publishing_profiles', ''))));
     334                            }
     335
     336                            foreach ($profiles as $i => $profile) {
     337                                $profileId = $profile->id;
     338                                */?>
     339                                <li>
     340                                    <p class="title"><?php /*echo $profile->formatted_service; */?>
     341                                        / <?php /*echo $profile->formatted_username; */?></p>
     342                                    <input data-id="<?php /*echo $profileId; */?>"
     343                                           class="expresscurate_displayNone expresscurate_social_publishing_profile"
     344                                           type="checkbox"
     345                                           id="expresscurate_social_publishing_<?php /*echo $profileId; */?>"
     346                                           name="expresscurate_social_publishing_<?php /*echo $profileId; */?>" <?php /*if ($profilesStatus->$profileId == 'on' || empty($profilesStatus->$profileId)) {
     347                                        echo 'checked="checked"';
     348                                    }  */?> />
     349                                    <label class="controls checkboxLabel"
     350                                           for="expresscurate_social_publishing_<?php /*echo $profileId; */?>"></label>
     351                                </li>
     352                            <?php
     353/*                            }
     354                            */?>
     355                            <input type="hidden" value="<?php /*echo stripslashes(get_option('expresscurate_social_publishing_profiles', '')); */?>" id="expresscurate_social_publishing_profiles" name="expresscurate_social_publishing_profiles"/>
     356                        <?php
     357/*                        }
     358                        */?>
     359                    </div>-->
     360
    317361                </ul>
    318362                <div class="centerSave">
  • expresscurate/trunk/templates/sitemap.php

    r1147120 r1162493  
    8484<li>
    8585    <p class="title">Sitemap manual priority value<span class="description ">Please select the priority for new posts in your sitemap</span></p>
    86     <select class="controls" name="expresscurate_sitemap_priority_manual_value">
     86    <select class="controls" id="expresscurate_sitemap_priority_manual_value" name="expresscurate_sitemap_priority_manual_value">
    8787        <?php for($i=0.1; $i<=1; $i=$i+0.1) {
    88             echo '<option value="'.$i.'"';
    89             if ($i == $priority) {
     88            echo '<option value="'.$i.'" ';
     89            if ((string)$i == $priority) {
    9090                echo ' selected="selected"';
    9191            }
     
    166166            </option>
    167167        </select>
    168         <?php 
     168        <?php
    169169        $blogName = urlencode(urlencode(get_bloginfo('url')));
    170170        ?>
  • expresscurate/trunk/templates/social_posts_widget.php

    r1140784 r1162493  
    11<?php
     2$buffer = new ExpressCurate_BufferClient();
     3$profiles = $buffer->getProfiles();
     4?>
     5<div class="expresscurate_social_post_widget">
     6    <ul class="mainControls">
     7        <li id="expresscurate_addTweet" class="expresscurate_social_widget_buttons">Create</li>
     8        <li class="expresscurate_social_widget_buttons expresscurate_social_get_content">Get content</li>
     9        <li data-header="h1" class="expresscurate_headerTweet expresscurate_social_widget_buttons">H1</li>
     10        <li data-header="h2" class="expresscurate_headerTweet expresscurate_social_widget_buttons">H2</li>
     11        <li data-header="h3" class="expresscurate_headerTweet expresscurate_social_widget_buttons">H3</li>
     12    </ul>
     13    <input id="expresscurate_postId" type="hidden" value="<?php echo the_ID(); ?>"/>
    214
    3 ?>
     15</div>
    416
    5 here will be some cool stuff, no kidding!
    6 
     17<script type="text/html" id="tmpl-socialPostWidget">
     18    <div class="expresscurate_tweetBlock">
     19        <ul class="topControls">
     20            <li class="close expresscurate_floatRight" ></li>
     21            <div class="expresscurate_clear"></div>
     22        </ul>
     23        <textarea name="" class="expresscurate_social_post_content expresscurate_disableInputStyle" id="">{{data}}</textarea>
     24        <ul class="bottomControls">
     25            <li class="expresscurate_social_widget_buttons approve">Approve</li>
     26        </ul>
     27    </div>
     28</script>
  • expresscurate/trunk/templates/sources_coll_widget.php

    r1106118 r1162493  
    44$is_json = true;
    55if ($pagenow == 'post-new.php' && $_POST && isset($_POST['expresscurate_bookmarks_curate_data'])) {
    6     $items = json_decode(stripslashes($_POST['expresscurate_bookmarks_curate_data']), true);
     6    $items = json_decode(stripslashes_deep($_POST['expresscurate_bookmarks_curate_data']), true);
    77} else if ($pagenow == 'post-new.php' && isset($_REQUEST['expresscurate_load_source'])) {
    88    $domain = parse_url(urldecode(base64_decode($_REQUEST['expresscurate_load_source'])));
    99    $items[0]['link'] = urldecode(base64_decode($_REQUEST['expresscurate_load_source']));
    1010    $items[0]['domain'] = $domain['host'];
    11     $items[0]['title'] = urldecode($_REQUEST['expresscurate_load_title']);
     11    $items[0]['title'] = stripslashes(urldecode($_REQUEST['expresscurate_load_title']));
    1212    $is_json = false;
    1313} else {
Note: See TracChangeset for help on using the changeset viewer.