Custom WP_Sitemaps_Provider sitemap page loading last blog post instead - php

I have updated to Wordpress 5.5 and want to remove Yoast from the install as its pretty much only used for sitemaps, however need to create a couple of custom sitemaps based on different post types, which I am currently doing with Yoast.
I am adding a custom provider as seen below which overrides both needed abstract functions. Both of these are working and the new entry is being added to the sitemap index at wp-sitemap.xml
However when clicking on /wp-sitemap-range-1.xml I get resolved with the latest blog post on the site instead of the expected site map with the three post types together.
I cannot find any documentation in the Wordpress API spec or codex yet so am at a bit of a loss at the moment - Any help is appreciated. A link with an example working provider would also be appreciated, as I have searched far to try and find something with no luck.
My next steps to to check all the 404 handlers and rewrite handlers in my theme to see if anything is sending it to the wrong place. I have much more complex sitemaps to produce, but want this one simple aggregation of the three post types to work first.
<?php
//Add provider for post types 'cast', 'keg', 'cider' to create a sitemap called 'range'
add_action('init', function() {
$rangeProvider = new key_sitemapProvider('range', array('cask', 'keg', 'cider'));
wp_register_sitemap_provider('pmrs-range', $rangeProvider);
});
​
/*---------------------*/
class key_sitemapProvider extends WP_Sitemaps_Provider {
public $postTypes = array();
​
/*---------------------*/
public function __construct($name, $postTypes) {
$this->name = $name;
$this->postTypes = $postTypes;
$this->object_type = 'post';
}
​
/*---------------------*/
private function queryArgs(){
return array(
'post_type' => $this->postTypes,
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'post_date',
'order' => 'DESC'
);
}
​
/*--OVERRIDE-----------*/
public function get_url_list($page_num, $post_type = '') {
$query = new WP_Query($this->queryArgs());
$urlList = array();
​
foreach($query->posts as $post) {
$sitemapEntry = array(
'chf' => 'weekly',
'pri' => 1.0,
'loc' => get_permalink($post),
'mod' => get_the_modified_time('Y-m-d H:i:s', $post)
);
$sitemapEntry = apply_filters('wp_sitemaps_posts_entry', $sitemapEntry, $post, $post_type);
$urlList[] = $sitemapEntry;
}
​
return $urlList;
}
​
/*--OVERRIDE-----------*/
public function get_max_num_pages($post_type = '') {
return 1;
}
/*---------------------*/
}
​
SOLUTION
Thanks to Matt Jaworski's answer below found a way to get this to work, with couple other issues.
Name property of your WP_Sitemaps_Provider extension needs to match the name you are registering with Wordpress. Also this should not contain any special chars
Supported values in the sitemap entries are : changefreq, priority, loc, and lastmod. This is opposed to the shortened version I had before.
Working Example
/*---------------------*/
add_action('init', function() {
$rangeProvider = new key_sitemapProvidor('range', array('cask', 'keg', 'cider'));
wp_register_sitemap_provider('range', $rangeProvider);
});
/*---------------------*/
class key_sitemapProvidor extends WP_Sitemaps_Provider {
public $postTypes = array();
/*---------------------*/
public function __construct($name, $postTypes) {
$this->name = $name;
$this->postTypes = $postTypes;
$this->object_type = 'post';
}
/*---------------------*/
private function queryArgs(){
return array(
'post_type' => $this->postTypes,
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'post_date',
'order' => 'DESC'
);
}
/*--OVERRIDE-----------*/
public function get_url_list($page_num, $post_type = '') {
$query = new WP_Query($this->queryArgs());
$urlList = array();
foreach($query->posts as $post) {
$sitemapEntry = array(
'changefreq' => 'weekly',
'priority' => 1.0,
'loc' => get_permalink($post),
'lastmod' => get_the_modified_time('Y-m-d H:i:s', $post)
);
$sitemapEntry = apply_filters('wp_sitemaps_posts_entry', $sitemapEntry, $post, $post_type);
$urlList[] = $sitemapEntry;
}
return $urlList;
}
/*--OVERRIDE-----------*/
public function get_max_num_pages($post_type = '') {
return 1;
}
/*---------------------*/
}

I had the same issue, and docs simply don't exist.
Through several trials and errors I figured out most likely WordPress does not like any special chars inside the names.
In my case replacing community-posts with communityposts helped.
Here's the very rough (but working) proof of concept we are working on now:
class PeepSo3_Sitemap_Provider extends WP_Sitemaps_Provider {
private $limit = 10; // #TODO CONFIGURABLE
public function __construct() {
$this->name = 'communityposts';
$this->object_type = 'communityposts';
}
private function sql($page_num) {
$sql =""; // your queries here;
return $wpdb->get_results($sql);
}
// retrieve a page of results
public function get_url_list( $page_num, $object_subtype = '' ) {
$url_list = [];
$posts = $this->sql($page_num);
foreach($posts as $post) {
$url_list[] = ['loc' => $post->url; // depends on your item structure
}
return $url_list;
}
// estimate how many pages are available
public function get_max_num_pages( $object_subtype = '' ) {
$posts = $this->sql(-1);
return ceil($posts[0]->count_posts/$this->limit);
}
}
// Register XML Sitemap Provider
add_filter('init', function() {
$provider = new PeepSo3_Sitemap_Provider();
wp_register_sitemap_provider( 'communityposts', $provider );
});

Related

Category isn't getting related video blogs?

I am trying to get category related video blogs by below code but i get nothing in var_dump? I want to get category related videos:
$category = VideoBlogCategoryModel::findFirst(1); // This returns category successfully and there are many video blogs having this category linked
var_dump($category->getVideoBlogs());exit;
VideoBlogModel.php
public function initialize(){
// Run base initialize code
parent::initialize();
// Configure Relation with VideoBlogCategoryModel
$this->belongsTo('category_id', VideoBlogCategoryModel::class, 'id', array(
'alias' => 'videoCategory',
'foreignKey' => true
));
}
public function getVideoCategory(){
return $this->videoCategory;
}
public function setVideoCategory($videoCategory){
$this->videoCategory = $videoCategory;
}
VideoBlogCategoryModel.php
public function initialize(){
// Run base initialize code
parent::initialize();
// Configure relation with VideoBlogModel
$this->hasMany('id', VideoBlogModel::class, 'category_id', array(
'alias' => 'videoBlogs',
'foreignKey' => true,
'cxAction' => static::ACTION_CASCADE_DELETE
));
}
public function getVideoBlogs(){
return $this->videoBlogs;
}
public function setVideoBlogs($videoBlogs){
$this->videoBlogs = $videoBlogs;
}
Let me know if anything else is required, I will share it.
In VideoBlogCategoryModel.php change
public function getVideoBlogs() {
return $this->videoBlogs;
}
to
public function getVideoBlogs() {
return $this->getRelated('videoBlogs');
}
Then try accessing it like:
$category = VideoBlogCategoryModel::findFirst(1);
$videos = $category->getVideoBlogs();
foreach( $videos as $video ) {
// access data here
var_dump($video->anyProperty()); // e.g $video->getId()
}
can you try it
$category = VideoBlogCategoryModel::findFirst(1);
$videos = $category->getVideoBlogs();
var_dump($videos->count());
var_dump($videos->toArray());
exit;
I think use var_dump for a Phalcon Collection Object is not a good idea, you can convert it to Array and Var_dump
Hope it can help you
Or try:
$categories = VideoBlogCategoryModel::findById($id);

how do i add an array of objects or an extra field to a wordpress json api core controller?

Is there a way to add an array of objects to a core controller of the json apiplugin
the core controller i want to modify is 'get_category_index' and it returns these objects
id
post_count
name
slug
what i want to do is an extra object, 'posts' which lists the posts that are assigned to that category
the code currently looks like this in jsonapi/controllers/core.php for that specific controller looks like that
public function get_category_index() {
global $json_api;
$args = null;
if (!empty($json_api->query->parent)) {
$args = array(
'parent' => $json_api->query->parent
);
}
$categories = $json_api->introspector->get_categories($args);
return array(
'count' => count($categories),
'categories' => $categories
);
}
and i thought something like this would do the trick
public function get_category_index() {
global $json_api;
$args = null;
if (!empty($json_api->query->parent)) {
$args = array(
'parent' => $json_api->query->parent
);
}
$categories = $json_api->introspector->get_categories($args);
return array(
'count' => count($categories),
'categories' => $categories,
'posts' => $posts,
);
}
but i'm obviously im missing something, how can i add that array of posts?
i found the category.php which looks like this
class JSON_API_Category {
var $id; // Integer
var $slug; // String
var $title; // String
var $description; // String
var $parent; // Integer
var $post_count; // Integer
var $posts; // array
function JSON_API_Category($wp_category = null) {
if ($wp_category) {
$this->import_wp_object($wp_category);
}
}
function import_wp_object($wp_category) {
$this->id = (int) $wp_category->term_id;
$this->slug = $wp_category->slug;
$this->title = $wp_category->name;
$this->description = $wp_category->description;
$this->parent = (int) $wp_category->parent;
$this->post_count = (int) $wp_category->count;
$this->posts = $wp_category->posts;
}
}
where i added the
var $posts; // array
and called it
$this->posts = $wp_category->posts;
however obviously i'm getting null on posts in my json return, whereas i need it to return the posts in that category.

SilverStripe - Custom Faceted Search Navigation

I'm working on page for a SilverStripe that will allow users to sort through portfolio pieces based on the selected facets.
Here are the key points/requirements:
I have 2 facet categories they can search by: Media Type (i.e. Ads,
Posters, TV, Web) and Industry (Entertainment, Finance, Healthcare,
Sport, etc).
Users should be allowed to search multiple facets at once and also
across media type and industry at once.
In the SilverStripe admin, since content managers need to be able
to maintain the facet namesfor Media Type and Industry, I made it so
that there are 2 admin models where the names can be entered:
MediaTypeTagAdmin and IndustryTagAdmin. Here are the data object
classes for MediaTypeTag and IndustryTag that are used by the admin
models:
MediaTypeTag class
<?php
class MediaTypeTag extends DataObject {
private static $db = array(
'Name' => 'varchar(250)',
);
private static $summary_fields = array(
'Name' => 'Title',
);
private static $field_labels = array(
'Name'
);
private static $belongs_many_many = array(
'PortfolioItemPages' => 'PortfolioItemPage'
);
// tidy up the CMS by not showing these fields
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName("PortfolioItemPages");
return $fields;
}
static $default_sort = "Name ASC";
}
IndustryTag class
<?php
class IndustryTag extends DataObject {
private static $db = array(
'Name' => 'varchar(250)',
);
private static $summary_fields = array(
'Name' => 'Title',
);
private static $field_labels = array(
'Name'
);
private static $belongs_many_many = array(
'PortfolioItemPages' => 'PortfolioItemPage'
);
// tidy up the CMS by not showing these fields
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName("PortfolioItemPages");
return $fields;
}
static $default_sort = "Name ASC";
}
There needs to be a page for each Portfolio Item, so I made a PortfolioItemPage type, which has 2 tabs: one for Media Type and one for Industry Type. This is so content managers can associated whatever tags they want with each Portfolio Item by checking the appropriate boxes:
PortfolioItemPage.php file:
private static $db = array(
'Excerpt' => 'Text',
);
private static $has_one = array(
'Thumbnail' => 'Image',
'Logo' => 'Image'
);
private static $has_many = array(
'PortfolioChildItems' => 'PortfolioChildItem'
);
private static $many_many = array(
'MediaTypeTags' => 'MediaTypeTag',
'IndustryTags' => 'IndustryTag'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
if ($this->ID) {
$fields->addFieldToTab('Root.Media Type Tags', CheckboxSetField::create(
'MediaTypeTags',
'Media Type Tags',
MediaTypeTag::get()->map()
));
}
if ($this->ID) {
$fields->addFieldToTab('Root.Industry Tags', CheckboxSetField::create(
'IndustryTags',
'Industry Tags',
IndustryTag::get()->map()
));
}
$gridFieldConfig = GridFieldConfig_RecordEditor::create();
$gridFieldConfig->addComponent(new GridFieldBulkImageUpload());
$gridFieldConfig->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'EmbedURL' => 'YouTube or SoundCloud Embed Code',
'Thumb' => 'Thumb (135px x 135px)',
));
$gridfield = new GridField(
"ChildItems",
"Child Items",
$this->PortfolioChildItems(),
$gridFieldConfig
);
$fields->addFieldToTab('Root.Child Items', $gridfield);
$fields->addFieldToTab("Root.Main", new TextareaField("Excerpt"), "Content");
$fields->addFieldToTab("Root.Main", new UploadField('Thumbnail', "Thumbnail (400x x 400px)"), "Content");
$fields->addFieldToTab("Root.Main", new UploadField('Logo', "Logo"), "Content");
return $fields;
}
}
class PortfolioItemPage_Controller extends Page_Controller {
private static $allowed_actions = array (
);
public function init() {
parent::init();
}
}
What I thought might be a good approach would be to use jQuery and AJAX to send the ids of the selected facets to the server:
(function($) {
$(document).ready(function() {
var industry = $('.industry');
var media = $('.media');
var tag = $('.tag');
var selectedTags = "";
tag.each(function(e) {
$(this).bind('click', function(e) {
e.preventDefault();
$(this).addClass('selectedTag');
if(selectedTags.indexOf($(this).text()) < 0){
if($(this).hasClass('media')){
selectedTags += + $(this).attr("id") + "," +"media;";
}
else{
selectedTags += + $(this).attr("id") + "," +"industry;";
}
}
sendTag(selectedTags);
}.bind($(this)));
});
function sendTag(TagList){
$.ajax({
type: "POST",
url: "/home/getPortfolioItemsByTags/",
data: { tags: TagList },
dataType: "json"
}).done(function(response) {
var div = $('.portfolioItems');
div.empty();
for (var i=0; i<response.length; i++){
div.append(response[i].name + "<br />");
//return portfolio data here
}
})
.fail(function() {
alert("There was a problem processing the request.");
});
}
});
}(jQuery));
Then on Page.php, I loop through the ids and get the corresponding PortfolioItemPage information based on the facet ids:
public function getPortfolioItemsByTags(){
//remove the last comma from the list of tag ids
$IDs = $this->getRequest()->postVar('tags');
$IDSplit = substr($IDs, 0, -1);
//put the tag ids and their tag names (media or industry) into an array
$IDListPartial = explode(";",$IDSplit);
//This will hold the associative array of ids to types (i.e. 34 => media)
$IDListFinal = array();
array_walk($IDListPartial, function($val, $key) use(&$IDListFinal){
list($key, $value) = explode(',', $val);
$IDListFinal[$key] = $value;
});
//get Portfolio Items based on the tag ids and tag type
foreach($IDListFinal as $x => $x_value) {
if($x_value=='media'){
$tag = MediaTypeTag::get()->byId($x);
$portfolioItems = $tag->PortfolioItemPages();
}
else{
$tag = IndustryTag::get()->byId($x);
$portfolioItems = $tag->PortfolioItemPages();
}
$return = array();
foreach($portfolioItems as $portfolioItem){
$return[] = array(
'thumbnail' => $portfolioItem->Thumbnail()->Link(),
'name' => $portfolioItem->H1,
'logo' => $portfolioItem->Logo()->Link(),
'excerpt' => $portfolioItem->Excerpt,
'id' => $portfolioItem->ID
);
}
return json_encode($return);
}
}
However, this is where I am getting stuck. While I have found some decent examples of building a PHP/MySQL faceted search outside of a CMS, I am not sure what I can modify in order to make the search work inside a CMS. That, and the examples put the facets in one table in the MySQL database whereas I have 2 (As much as I want to have just one MySQL table for both the Media Type and Industry facets, I am not sure if this is a good idea since content managers want to maintain the facet names themselves).
Are there any tutorials out there that might provide further assistance, or possibly a plugin that I have not found yet? If there is a better way to set this faceted search up, by all means, please suggest ideas. This is pretty new to me.
The most efficient way to do this is to filter based on the tag/media type IDs in one query (your example is doing one database query per tag/type, then appending the results).
You should be able to do something like this:
<?php
public function getPortfolioItemsByTags(){
$tagString = $this->getRequest()->postVar('tags');
// remove the last comma from the list of tag ids
$tagString = substr($tagString, 0, -1);
//put the tag ids and their tag names (media or industry) into an array
$tags = explode(";", $tagString);
//This will hold the associative array of ids to types (i.e. 34 => media)
$filters = array(
'media' => array(),
'industry' => array()
);
array_walk($tags, function($val, $key) use(&$filters) {
list($id, $type) = explode(',', $val);
$filters[$type][] = $id;
});
$portfolioItems = PortfolioItemPage::get()->filterAny(array(
'MediaTypeTags.ID' => $filters['media'],
'IndustryTags.ID' => $filters['industry']
));
$return = array();
foreach($portfolioItems as $portfolioItem){
$return[] = array(
'thumbnail' => $portfolioItem->Thumbnail()->Link(),
'name' => $portfolioItem->H1,
'logo' => $portfolioItem->Logo()->Link(),
'excerpt' => $portfolioItem->Excerpt,
'id' => $portfolioItem->ID
);
}
return json_encode($return);
}

Magento ProductController append categoryId to products

I am adding a mass action to add a category. I am most of the way there I only have one function left to figure out.
Clr\Categorymassaction\controllers\Adminhtml\Catalog\ProductController.php
class Clr_Categorymassaction_Adminhtml_Catalog_ProductController extends Mage_Adminhtml_Controller_Action
{
public function massCategoryAction()
{
$productIds = $this->getRequest()->getParam('product');
$cat = $this->getRequest()->getParam('Category');
if (!is_array($productIds)) {
$this->_getSession()->addError($this->__('Please select product(s).'));
$this->_redirect('*/*/index');
}
else {
$cat = $category['label']->getCategoryId();
foreach($productIds as $product) {
//Process $cat into categoryId append categoryId to $productId
$cat->setPostedProducts($product);
}
//Save product
$cat->save();
}
}
}
Clr\Categorymassaction\Model\Observer
class Clr_Categorymassaction_Model_Observer {
public function addCategoryMassAction(Varien_Event_Observer $observer)
{
$block = $observer ->getBlock();
if ($block instanceof Mage_Adminhtml_Block_Catalog_Product_Grid) {
$block->getMassactionBlock()->addItem('Clr_Categorymassaction', array(
'label' => Mage::helper('catalog')->__('Add to Category'),
'url' => $block->getUrl('*/*/massCategory', array('_current' => true)),
'additional'=> array(
'visibility' => array(
'name' =>'Category',
'class' =>'required-entry',
'label' =>Mage::helper('catalog')->__('Categories'),
'type' => 'select',
'values' => Mage::getModel('Categorymassaction/system_config_source_category')->toOptionArray(),
'renderer' => 'Categorymassaction/catalog_product_grid_render_category',
)
)
));
};
}
}
One last thing
class Clr_Categorymassaction_Model_System_Config_Source_Category
{
public function toOptionArray($addEmpty = true)
{
$options = array();
foreach ($this->load_tree() as $category) {
$options[$category['value']] = $category['label'];
}
return $options;
}
I am mostly in trouble here because I am refactoring, Flagbit_changeattributeset and Vuleticd_AdminGridCategoryFilter. I know what I need to do (at least I think I do) I just don't know how to finish this off. Thanks for your eyes and ears if you read it all.
UPDATE: The observer from Vuleticd_AdminGridCategoryFilter had this additional code
'filter_condition_callback' => array($this, 'filterCallback'),
)
)
));
};
}
public function filterCallback($collection, $column)
{
$value = $column->getFilter()->getValue();
$_category = Mage::getModel('catalog/category')->load($value);
$collection->addCategoryFilter($_category);
return $collection;
}
This was used to apply the filter to the grid. What I am trying to do is instead of using the dropdown to filter column fields; use the dropdown to trigger the ProductController to pass the selected items a new categoryid.
https://magento.stackexchange.com/questions/67234/productcontroller-for-mass-action Asked this question over at magento's stackexchange figured I would post the link here for posterity.

Using pagination with a custom model method in CakePHP

I'm setting up pagination to display a list of images belonging to the user in their account. This is what I have in my controller:
class UsersController extends AppController {
public $paginate = array(
'limit' => 5,
'order' => array(
'Image.uploaded' => 'DESC'
)
);
// ...
public function images() {
$this->set('title_for_layout', 'Your images');
$albums = $this->Album->find('all', array(
'conditions' => array('Album.user_id' => $this->Auth->user('id'))
));
$this->set('albums', $albums);
// Grab the users images
$options['userID'] = $this->Auth->user('id');
$images = $this->paginate('Image');
$this->set('images', $images);
}
// ...
}
It works, but before I implemented this pagination I had a custom method in my Image model to grab the users images. Here it is:
public function getImages($options) {
$params = array('conditions' => array());
// Specific user
if (!empty($options['userID'])) {
array_push($params['conditions'], array('Image.user_id' => $options['userID']));
}
// Specific album
if (!empty($options['albumHash'])) {
array_push($params['conditions'], array('Album.hash' => $options['albumHash']));
}
// Order of images
$params['order'] = 'Image.uploaded DESC';
if (!empty($options['order'])) {
$params['order'] = $options['order'];
}
return $this->find('all', $params);
}
Is there a way I can use this getImages() method instead of the default paginate()? The closest thing I can find in the documentation is "Custom Query Pagination" but I don't want to write my own queries, I just want to use the getImages() method. Hopefully I can do that.
Cheers.
Yes.
//controller
$opts['userID'] = $this->Auth->user('id');
$opts['paginate'] = true;
$paginateOpts = $this->Image->getImages($opts);
$this->paginate = $paginateOpts;
$images = $this->paginate('Image');
//model
if(!empty($opts['paginate'])) {
return $params;
} else {
return $this->find('all', $params);
}
Explanation:
Basically, you just add another parameter (I usually just call it "paginate"), and if it's true in the model, instead of passing back the results of the find, you pass back your dynamically created parameters - which you then use to do the paginate in the controller.
This lets you continue to keep all your model/database logic within the model, and just utilize the controller to do the pagination after the model builds all the complicated parameters based on the options you send it.

Categories