How do I add nested JSON-LD Schema entities in WordPress - php

Rather than using a plug-in I am in the process of creating my own schema markup for a home page on a WordPress site which makes use of Advananced Custom Fields (ACF) for some of the content relevant to this challenge. My aim is to give me a little more granular control over what is output and as a little personal challenge :)
So far I have successfully created a basic schema, but I have become stuck where I need to create a nested entity for a list of services.
currently I have this in my functions.php file:
function schema() {
$schema = array(
'#context' => "http://schema.org",
'#type' => "ProfessionalService",
'name' => get_bloginfo('name'),
'url' => get_home_url(),
// ... and so on
);
if ( have_rows('services_list') ) {
$schema['itemListElement'] = array();
while (have_rows('services_list')) : the_row();
$services = array(
'#type' => 'Offer',
'itemOffered' => array (
'#type' => 'Service',
'name' => get_sub_field('service')
)
);
array_push($schema['itemListElement'], $services);
endwhile;
}
echo '<script type="application/ld+json">' . json_encode($schema) . '</script>';
}
add_action('wp_head', 'schema');
Result:
{
"#context":"http:\/\/schema.org",
"#type":"ProfessionalService",
"name":"Name of Company",
"url":"http:\/\/www.whatever.com",
and so on...
"itemListElement": [
{
"#type":"Offer",
"itemOffered": {
"#type":"Service",
"name":"Service One"
}
},
{
"#type":"Offer",
"itemOffered": {
"#type":"Service",
"name":"Service Two"
}
},
... more services
]
}
This resulting markup is great, but I need to nest the itemListElement so that it outputs like so:
"hasOfferCatalog": {
"#type": "OfferCatalog",
"name": "Some Services",
"itemListElement": [
{
"#type":"Offer",
"itemOffered": {
"#type":"Service",
"name":"Service One"
}
},
{
"#type":"Offer",
"itemOffered": {
"#type":"Service",
"name":"Service Two"
}
},
... more services
I can't for the life of me work out how this is done. My current best effort is to add it like so:
if ( have_rows('services_list') ) {
'hasOfferCatalog' => array(
'#type' => 'OfferCatalog',
'name' => 'Tree Surgery'
$schema['itemListElement'] = array();
while (have_rows('services_list')) : the_row();
$services = array(
'#type' => 'Offer',
'itemOffered' => array (
'#type' => 'Service',
'name' => get_sub_field('service')
)
);
array_push($schema['itemListElement'], $services);
endwhile;
)
}
However this is not working at all. If anyone can point me in the right direction to the nested entities working in this context I'd be really grateful.

I did eventually manage to solve my issue. I never got around to posting this at he time, but since here's been a little interest here's what I did.
My 'best effort' was almost there, but I needed to make a few small adjustments. Sadly I sorted this out sometime ago and I forget the resource I used to put things right, but I hope this might be of help to someone else.
if ( have_rows('services_list') ) {
$schema['hasOfferCatalog'] = array();
$catalog = array(
'#type' => 'OfferCatalog',
'name' => 'Tree Surgery'
);
if ( have_rows('services_list') ) {
$catalog['itemListElement'] = array();
while (have_rows('services_list')) : the_row();
$services = array(
'#type' => 'Offer',
'itemOffered' => array (
'#type' => 'Service',
'name' => get_sub_field('service')
)
);
array_push($catalog['itemListElement'], $services);
endwhile;
array_push($schema['hasOfferCatalog'], $catalog);
}
}
Fr a little bit of context I have this all placed in my functions.php file and is put together like so:
function schema() {
$schema = array(
'#context' => "http://schema.org",
'#type' => "ProfessionalService",
'name' => get_bloginfo('name'),
'url' => get_home_url(),
'telephone' => '+00 0000 00000',
'address' => array(
'#type' => 'PostalAddress',
'streetAddress' => 'XXXXX',
'postalCode' => 'XXX XXX',
'addressLocality' => 'XXXXXX',
'addressRegion' => 'XXXXXXX',
'addressCountry' => 'XXXXXXXXXXXX'
),
'logo' => get_stylesheet_directory_uri() . '/path/to/your/image.svg',
'image' => get_stylesheet_directory_uri() . '/path/to/your/image.svg'
);
if ( have_rows('services_list') ) {
$schema['hasOfferCatalog'] = array();
$catalog = array(
'#type' => 'OfferCatalog',
'name' => 'Tree Surgery'
);
if ( have_rows('services_list') ) {
$catalog['itemListElement'] = array();
while (have_rows('services_list')) : the_row();
$services = array(
'#type' => 'Offer',
'itemOffered' => array (
'#type' => 'Service',
'name' => get_sub_field('service')
)
);
array_push($catalog['itemListElement'], $services);
endwhile;
array_push($schema['hasOfferCatalog'], $catalog);
}
}
echo '<script type="application/ld+json">' . json_encode($schema) . '</script>';
}
add_action('wp_head', 'schema');
It seems to do the job.

Related

Mailchimp API send campaign

I have written a custom Drupal 8 module that will send article notifications to our Mailchimp subscribers.
Problem
Currently, the module loops over each of the article 'tags' and sends out a separate campaign for each. Consequently, if someone is subscribed two of an article's tags, they will receive two emails.
Solution
Instead, I'd like each person to receive a maximum of one email notification. I think that the best way to achieve this is to create a dynamic segment. Similar to the query posted here. However, I have been getting various errors, and can't find much help here and here.
Code
<?php
function mailchimp_notification_process(EntityInterface $node)
{
global $base_url;
$settings = Settings::get('mailchimp_audience');
$content = (object) array(
'subject_line' => 'XXX',
'title' => 'Notification',
'from_name' => 'XXX',
'reply_to' => 'noreply#gmail.com'
);
$template_content = [
'html' => [
'value' => '<table align="center" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="bodyTable">email content removed for brevity</table>',
'format' => 'mailchimp_campaign'
]
];
$mc_campaign = mailchimp_get_api_object('MailchimpCampaigns');
$lists = mailchimp_campaign_get_list_segments($settings, NULL);
if (
$node->isPublished() &&
($node->bundle() == 'article' || $node->bundle() == 'resource') &&
$node->field_send_notification->getString() == 1 &&
isset($node->field_send_notification_to->entity)
) {
$all_tags = [];
// loop over each of the loops to send notifications to
foreach ($node->field_send_notification_to as $category) {
$tags = array_filter($lists, function ($list) use ($category) {
return $list->name == $category->entity->getName();
});
if ($tags && is_array($tags)) {
$tags = reset($tags);
array_push($all_tags, $tags->id);
\Drupal::logger('mailchimp_notification')->notice('all_tags: ' . json_encode($all_tags));
}
}
$recipients = (object) [
'list_id' => $settings,
'segment_opts' => [
'match' => 'all',
'conditions' => [
[
'condition_type' => 'Tags',
'field' => 'tags-123',
'op' => 'interestcontainsall',
'value' => $all_tags,
]
]
]
];
\Drupal::logger('mailchimp_notification')->notice('recipients: ' . json_encode($recipients));
$campaign_id = mailchimp_campaign_save_campaign($template_content, $recipients, $content, "", "");
if ($campaign_id) {
$mc_campaign->send($campaign_id);
}
}
}

Parsedown: sub/superscript

The current version of Parsedown 1.8.0-beta-5 doesn't have a builtin syntax for sub/superscript. Although CommonMark doesn't specify such syntax, several other lightweight markup languages (ex: Parsedown Extreme, Textile) use a syntax similar to the following:
in: 19^th^
out: 19<sup>th</sup>
in: H~2~O
out: H<sub>2</sub>O
Question
What steps should be taken in order to modify Parsedown.php file and include such syntax?
Note: This issue has already come up other times (Parsedown, add sub/superscript). However, there is still no step-by-step guide explaining what modifications should be done in Parsedown.php file in order to achieve that.
Append Superscript and Tilde in $InlineTypes:
protected $InlineTypes = array(
'!' => array('Image'),
'&' => array('SpecialCharacter'),
'*' => array('Emphasis'),
':' => array('Url'),
'<' => array('UrlTag', 'EmailTag', 'Markup'),
'[' => array('Link'),
'_' => array('Emphasis'),
'`' => array('Code'),
'~' => array('Tilde'),
'^' => array('Superscript'),
'\\' => array('EscapeSequence'),
);
Define methods inlineSuperscript. It should look pretty much like inlineStrikethrough:
protected function inlineSuperscript($Excerpt)
{
if (preg_match('/^\^(.+?)\^/', $Excerpt['text'], $matches))
{
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'sup',
'handler' => array(
'function' => 'lineElements',
'argument' => $matches[1],
'destination' => 'elements',
)
),
);
}
}
Define methods inlineTilde and delete method inlineStrikethrough. It should look pretty much like inlineEmphasis:
protected function inlineTilde($Excerpt)
{
if ( ! isset($Excerpt['text'][1]))
{
return;
}
$marker = $Excerpt['text'][0];
if ($Excerpt['text'][1] === $marker and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
{
$emphasis = 'del';
}
elseif (preg_match('/^~(?=\S)(.+?)(?<=\S)~/', $Excerpt['text'], $matches))
{
$emphasis = 'sub';
}
else
{
return;
}
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => $emphasis,
'handler' => array(
'function' => 'lineElements',
'argument' => $matches[1],
'destination' => 'elements',
)
),
);
}
Add the new symbol to $inlineMarkerList:
protected $inlineMarkerList = '!*_&[:<`~\\^';

Codeigniter call one's Class method inside another Classe's method

To be very specific - there is CRM system written in Codeigniter called Rise. I would like to make (automatically) an expense entry ( call save() method of an Expenses class ) each time someone logs in ( inside save_timelog() method of a Projects class ) time manually.
Expense Controller:
function save() {
validate_submitted_data(array(
"id" => "numeric",
"expense_date" => "required",
"category_id" => "required",
"amount" => "required"
));
$id = $this->input->post('id');
$target_path = get_setting("timeline_file_path");
$files_data = move_files_from_temp_dir_to_permanent_dir($target_path, "expense");
$has_new_files = count(unserialize($files_data));
$data = array(
"expense_date" => $this->input->post('expense_date'),
"title" => $this->input->post('title'),
"description" => $this->input->post('description'),
"category_id" => $this->input->post('category_id'),
"amount" => unformat_currency($this->input->post('amount')),
"project_id" => $this->input->post('expense_project_id'),
"user_id" => $this->input->post('expense_user_id'),
"files" => $files_data
);
<.. ETC. CHECKING FILES ..>
$save_id = $this->Expenses_model->save($data, $id);
if ($save_id) {
echo json_encode(array("success" => true, "data" => $this->_row_data($save_id), 'id' => $save_id, 'message' => lang('record_saved')));
} else {
echo json_encode(array("success" => false, 'message' => lang('error_occurred')));
}
}
Projects Controller:
function save_timelog() {
$this->access_only_team_members();
$id = $this->input->post('id');
$start_time = $this->input->post('start_time');
$end_time = $this->input->post('end_time');
$note = $this->input->post("note");
$task_id = $this->input->post("task_id");
if (get_setting("time_format") != "24_hours") {
$start_time = convert_time_to_24hours_format($start_time);
$end_time = convert_time_to_24hours_format($end_time);
}
$start_date_time = $this->input->post('start_date') . " " . $start_time;
$end_date_time = $this->input->post('end_date') . " " . $end_time;
$start_date_time = convert_date_local_to_utc($start_date_time);
$end_date_time = convert_date_local_to_utc($end_date_time);
$data = array(
"project_id" => $this->input->post('project_id'),
"start_time" => $start_date_time,
"end_time" => $end_date_time,
"note" => $note ? $note : "",
"task_id" => $task_id ? $task_id : 0,
);
if (!$id) {
//insert mode
$data["user_id"] = $this->input->post('user_id') ? $this->input->post('user_id') : $this->login_user->id;
} else {
//edit mode
//check edit permission
$this->check_timelog_updte_permission($id);
}
$save_id = $this->Timesheets_model->save($data, $id);
if ($save_id) {
echo json_encode(array("success" => true, "data" => $this->_timesheet_row_data($save_id), 'id' => $save_id, 'message' => lang('record_saved')));
} else {
echo json_encode(array("success" => false, 'message' => lang('error_occurred')));
}
}
So now what I'm trying to do is inside Projects controller, save_timelog() method just below these lines:
<...>
if (!$id) {
//insert mode
$data["user_id"] = $this->input->post('user_id') ? $this->input->post('user_id') : $this->login_user->id;
} else {
//edit mode
//check edit permission
$this->check_timelog_updte_permission($id);
}
/* CREATING A SAMPLE ARRAY WITH STATIC DATA FOR AN EXAMPLE EXPENSE ENTRY */
$a = array(
"expense_date" => '2018-03-13',
"title" => 'Cat Food',
"description" => 'Sheba, Felix, KiteKat',
"category_id" => '85',
"amount" => '500',
"project_id" => '84',
"user_id" => '10',
"files" => $files_data
);
/* TRYING TO SAVE/SEND EXAMPLE ARRAY TO Expenses Class save() method (?) */
$b = $this->Expenses_model->save($a);
/* RESULT (?) */
$save_id = $this->Timesheets_model->save($data, $id);
if ($save_id) {
echo json_encode(
array(
array(
"success" => true,
"data" => $this->_timesheet_row_data($save_id),
'id' => $save_id,
'message' => lang('record_saved')
),
array(
"success" => true,
"data" => _row_data($b),
'id' => $save_id,
'message' => lang('record_saved')
)
)
);
} else {
echo json_encode(array("success" => false, 'message' => lang('error_occurred')));
}
<.. Closing save_timelog() method ..>
However it surely doesn't work and all I get is "POST http://rise.test/index.php/projects/save_timelog 500 (Internal Server Error)".
I also load Expenses model and Expenses categories model in Projects _construct():
Projects Controller:
public function __construct() {
parent::__construct();
$this->load->model("Project_settings_model");
$this->load->model("Expense_categories_model");
$this->load->model("Expenses_model");
}
I also contacted developers of Rise with following question/answer:
Me:
In Projects controller save_timelog() method I just want to call
Expenses controller save() method, and if save_timelog() is successful
I would like to save an Expense ( $this->Expenses_model->save($data,
$id); ) with appropriate data. Could be static values for now for
$data array in save() method - just to find out it's working.
Rise Devs:
Hi, You are doing almost right. Just remove the 2nd parameter $id. It
should be used only for update. $this->Expenses_model->save($data)
Would really appreciate any help and directions! Thanks.

Curl Exception 7 PHP and Guzzle with Elasticsearch

I am trying to index documents using the php client for elastic search which uses Guzzle. After compiling my php script I am getting an error that says Internal Server Error, code 500. After doing some research this seems to be an issue with connecting to a server but the strange part is that everything I'm trying to do is set up on the same machine. My instance of Elasticsearch, my documents I'm trying to index, and my php scripts are all saved and running on the same machine. This is my PHP Script:
<?php
require '/home/aharmon/vendor/autoload.php';
$client = new Elasticsearch\Client();
$root = realpath('/home/aharmon/elkdata/for_elk_test_2014_11_24/Agencies');
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST,
RecursiveIteratorIterator::CATCH_GET_CHILD);
$paths = array($root);
foreach ($iter as $path => $dir) {
if ($dir -> isDir()) {
$paths[] = $path;
}
}
//Create the index and mappings
$mapping['index'] = 'rvuehistoricaldocuments2009-2013'; //mapping code
$mapping['body'] = array (
'mappings' => array (
'documents' => array (
'_source' => array (
'enabled' => true
),
'properties' => array(
'doc_name' => array(
'type' => 'string',
'analyzer' => 'standard'
),
'description' => array(
'type' => 'string'
)
)
)
)
);
//Now index the documents
for ($i = 0; $i <= 10000; $i++) {
$params ['body'] [] = array(
'index' => array(
'_id' => $i
)
);
$params ['body'] [] = array(
'type' => 'documents',
'body' => array(
'foo' => 'bar'//Document body goes here
)
);
//Every 1000 documents stop and send the bulk request.
if($i % 1000) {
$responses = $client->bulk($params);
// erase the old bulk request
$params = array();
// unset the bulk response when you are done to save memory
unset($responses);
}
}
$client ->indices()->create($mapping)
?>
If anyone has seen this before or has an inclination as to what the issue the help would be greatly appreciated. I had a similar issue before when I tried to set up SSH but I got the firewall all configured and got SSH working so I'm not sure why this is happening.
check this link it is ok for me :
http://www.elastic.co/guide/en/elasticsearch/client/php-api/current/_index_operations.html#_put_mappings_api
<?php
// Set the index and type
$params['index'] = 'my_index';
$params['type'] = 'my_type2';
// Adding a new type to an existing index
$myTypeMapping2 = array(
'_source' => array(
'enabled' => true
),
'properties' => array(
'first_name' => array(
'type' => 'string',
'analyzer' => 'standard'
),
'age' => array(
'type' => 'integer'
)
)
);
$params['body']['my_type2'] = $myTypeMapping2;
// Update the index mapping
$client->indices()->putMapping($params);

How to create nodes using node_save?

I'm trying to migrate my current html site into drupal. I have over 80,000 pages I have to migrate so I thought instead of sitting infront of a computer for 50 years I would create a module. I was able to create a script that extracts the html from each directory and now I got to a road block where I need to create a node. I'm trying to create a new node using node_save(), but when node_save is executed, I get a PDOException error with everything I try. I'm passing in $node, which is an array which is then casted into an object.
PDOException: in field_sql_storage_field_storage_write() (line 424 of /srv/www/htdocs/modules/field/modules/field_sql_storage/field_sql_storage.module).
This is how we are currently creating the node, but it produces an error:
$node= array(
'uid' => $user->uid,
'name' => $user->name,
'type' => 'page',
'language' => LANGUAGE_NONE,
'title' => $html['title'],
'status' => 1,
'promote' => 0,
'sticky' => 0,
'created' => (int)REQUEST_TIME,
'revision' => 0,
'comment' => '1',
'menu' => array(
'enabled' => 0,
'mlid' => 0,
'module' => 'menu',
'hidden' => 0,
'has_children' => 0,
'customized' => 0,
'options' => array(),
'expanded' => 0,
'parent_depth_limit' => 8,
'link_title' => '',
'description' => '',
'parent' => 'main-menu:0',
'weight' => '0',
'plid' => '0',
'menu_name' => 'main-menu',
),
'path' => array(
'alias' => '',
'pid' => null,
'source' => null,
'language' => LANGUAGE_NONE,
'pathauto' => 1,
),
'nid' => null,
'vid' => null,
'changed' => '',
'additional_settings__active_tab' => 'edit-menu',
'log' => '',
'date' => '',
'submit' => 'Save',
'preview' => 'Preview',
'private' => 0,
'op' => 'Save',
'body' => array(LANGUAGE_NONE => array(
array(
'value' => $html['html'],
'summary' => $link,
'format' => 'full_html',
),
)),
'validated' => true,
);
node_save((object)$node);
// Small hack to link revisions to our test user.
db_update('node_revision')
->fields(array('uid' => $node->uid))
->condition('vid', $node->vid)
->execute();
Usually I create a bulkimport.php script in the document root to do this kind of thing.
Here is the code I use for Drupal 7:
<?php
define('DRUPAL_ROOT', getcwd());
include_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
function _create_node($title="", $body="", $language="und") {
$node = (object)array();
$node->uid = '1';
$node->name = 'admin';
$node->type = 'page';
$node->status = 1;
$node->promote = 0;
$node->sticky = 0;
$node->revision = 1;
$node->language = $language;
$node->title = $title;
$node->body[$language][0] = array(
'value' => $body,
'format' => 'full_html',
);
$node->teaser = '';
$node->log = 'Auto Imported Node';
node_submit($node);
node_save($node);
return $node;
} ### function _create_node
$sith = array(
'Darth Vader' => 'Master: Darth Sidious',
'Darth Sidious' => 'Master: Darth Plagous',
'Darth Maul' => 'Master: Darth Sidious',
'Darth Tyranous' => 'Master: Darth Sidious',
);
foreach($sith as $title=>$body) {
print "Creating Node. Title:[".$title."] \tBody:[".$body."] ";
$node = _create_node($title, $body);
print "\t... Created Node ID: [".$node->nid."]\n";
#print_r($node);
} ### foreach
I was getting exactly the same error as in your original post -
PDOException: in field_sql_storage_field_storage_write()
- etc.
I was already using the stdClass code style shown in the comments above.
The problem turned out to be the pound signs and accented chars in the string I was assigning to the Body field; the strings were coming from a Windows text file.
Converting the string to the Drupal target encoding (UTF-8) worked for me:
$cleaned_string = mb_convert_encoding($html['html'], "UTF-8", "Windows-1252");
$node->body[LANGUAGE_NONE][0]['value'] = $cleaned_string;
$node->body[LANGUAGE_NONE][0]['format'] = 'plain_text';
node_save($node);
Hope this helps someone.
are you using some CMS engine, or it is custom-written website?
in first case, look here http://drupal.org/documentation/migrate
I strongly recommend you to look at the given modules, they should help. Also, as an option, to migrate DB.
You don't need a lot of those blank fields. Also, you can cast the node as an object rather than an array converted to an object.
Your code should be able to be accomplished with this much shorter snippet:
$node = new stdClass();
$node->title = $html['title'];
$node->type = 'page';
$node->body['und'][0]['value'] = $html['html'];
node_save($node);
Also, there's a number of methods that have been developed to mass-import nodes into Drupal - I am a fan of the Feeds module (http://drupal.org/project/feeds). This may require writing a method of exporting your existing content to an intermediary format (CSV or XML), but it works reliably and you can re-import nodes to update content when required.

Categories