Shortcode API
A single shortcode like
[myshortcode]
Shortcodes with attributes like
[myshortcode id="1" type="text"]
Enclosing shortcodes like
[myshortcode id="1"] ... some content here ... [/myshortcode]
<?
php
/*
shortcode callback for [msg] shortcode
Example: [msg type="error"]This is an error message.[/msg]
Output:
<div class="message message-error">
<p>This is an error message.</p>
</div>
*/
function
sp_msg_shortcode
(
$atts
,
$content
)
{
//default attributes
extract
(
shortcode_atts
(
array
(
'type'
=>
'information'
,
),
$atts
)
);
$content
=
do_shortcode
(
$content
);
//allow nested shortcodes
$r
=
'<div class="message message-'
.
$type
.
'"><p>'
.
$content
.
'</p></div>'
;
return
$r
;
}
add_shortcode
(
'msg'
,
'sp_msg_shortcode'
);
?>
Shortcode Attributes
Nested Shortcodes
[msg type="error"] An error has occurred. Use the following link for help: [help_link]. [/msg]
Caution
<?
php
global
$post
;
$sidebar_content
=
$post
->
sidebar_content
;
?>
<div class="post">
<?php
the_content
();
?>
</div>
<div class="sidebar">
<?php
//echo do_shortcode($sidebar_content);
echo
apply_filters
(
'the_content'
,
$sidebar_content
);
?>
</div>
Removing Shortcodes
Note
//make a copy of the original shortcodes
global
$shortcode_tags
;
$original_shortcode_tags
=
$shortcode_tags
;
//remove the [msg] shortcode
unset
(
$shortcode_tags
[
'msg'
]);
//do shortcodes and echo
$content
=
do_shortcode
(
$content
);
echo
$content
;
//restore the original shortcodes
$shortcode_tags
=
$original_shortcode_tags
;
Other Useful Shortcode-Related Functions
shortcode_exists( $tag )
has_shortcode( $content, $tag )
Checks whether the shortcode
$tag
appears within the$content
variable.shortcode_parse_atts( $text )
Pulls attributes out of a shortcode. This is done for you when parsing a shortcode, but can be called directly if you want to pull attributes out of other text like HTML tags or other templates.
strip_shortcodes( $text )
Strips all shortcodes out of the
$text
variable and replaces them with empty text instead of calling the callback function.
Widgets API
Note
Before You Add Your Own Widget
Adding Widgets
/*
Taken from the Widgets API Codex Page at:
http://codex.wordpress.org/Widgets_API
*/
class
My_Widget
extends
WP_Widget
{
public
function
__construct
()
{
// widget actual processes
}
public
function
widget
(
$args
,
$instance
)
{
// outputs the content of the widget
}
public
function
form
(
$instance
)
{
// outputs the options form on admin
}
public
function
update
(
$new_instance
,
$old_instance
)
{
// processes widget options to be saved
}
}
add_action
(
'widgets_init'
,
function
(){
register_widget
(
'My_Widget'
);
});
/*
Taken from the Widgets API Codex Page at:
http://codex.wordpress.org/Widgets_API
*/
add_action
(
'widgets_init'
,
create_function
(
''
,
'return register_widget("My_Widget");'
)
);
Example 7-1. SchoolPress note widget
<?
php
/*
Widget to show the current class note.
Teachers (Group Admins) can change note for each group.
Shows the global note set in the widget settings if non-empty.
*/
class
SchoolPress_Note_Widget
extends
WP_Widget
{
public
function
__construct
()
{
parent
::
__construct
(
'schoolpress_note'
,
'SchoolPress Note'
,
array
(
'description'
=>
'Note to Show on Group Pages'
);
}
public
function
widget
(
$args
,
$instance
)
{
global
$current_user
;
//saving a note edit?
if
(
!
empty
(
$_POST
[
'schoolpress_note_text'
]
)
&&
!
empty
(
$_POST
[
'class_id'
]
)
)
{
//make sure this is an admin
if
(
groups_is_user_admin
(
$current_user
->
ID
,
intval
(
$_POST
[
'class_id'
]))){
//should escape the text and possibly use a nonce
update_option
(
'schoolpress_note_'
.
intval
(
$_POST
[
'class_id'
]
),
$_POST
[
'schoolpress_note_text'
]
);
}
}
//look for a global note
$note
=
$instance
[
'note'
];
//get class id for this group
$class_id
=
bp_get_current_group_id
();
//look for a class note
if
(
empty
(
$note
)
&&
!
empty
(
$class_id
)
)
{
$note
=
get_option
(
"schoolpress_note_"
.
$class_id
);
}
//display note
if
(
!
empty
(
$note
)
)
{
?>
<div id="schoolpress_note">
<?php
echo
wpautop
(
$note
);
?>
</div>
<?php
//show edit for group admins
if
(
groups_is_user_admin
(
$current_user
->
ID
,
$class_id
)
)
{
?>
<a id="schoolpress_note_edit_trigger">Edit</a>
<div id="schoolpress_note_edit" style="display: none;">
<form action="" method="post">
<input type="hidden"
name="class_id"
value="
<?php
echo
intval
(
$class_id
);
?>
" />
<textarea name="schoolpress_note_text" cols="30" rows="5">
<?php
echo
esc_textarea
(
get_option
(
'schoolpress_note_'
.
$class_id
))
;
?>
</textarea>
<input type="submit" value="Save" />
<a id="schoolpress_note_edit_cancel" href="javascript:void(0);">
Cancel
</a>
</form>
</div>
<script>
jQuery(document).ready(function() {
jQuery('#schoolpress_note_edit_trigger').click(function(){
jQuery('#schoolpress_note').hide();
jQuery('#schoolpress_note_edit').show();
});
jQuery('#schoolpress_note_edit_cancel').click(function(){
jQuery('#schoolpress_note').show();
jQuery('#schoolpress_note_edit').hide();
});
});
</script>
<?php
}
}
}
public
function
form
(
$instance
)
{
if
(
isset
(
$instance
[
'note'
]
)
)
$note
=
$instance
[
'note'
];
else
$note
=
""
;
?>
<p>
<label for="
<?php
echo
$this
->
get_field_id
(
'note'
);
?>
">
<?php
_e
(
'Note:'
);
?>
</label>
<textarea id="
<?php
echo
$this
->
get_field_id
(
'note'
);
?>
"
name="
<?php
echo
$this
->
get_field_name
(
'note'
);
?>
">
<?php
echo
esc_textarea
(
$note
);
?>
</textarea>
</p>
<?php
}
public
function
update
(
$new_instance
,
$old_instance
)
{
$instance
=
array
();
$instance
[
'note'
]
=
$new_instance
[
'note'
];
return
$instance
;
}
}
add_action
(
'widgets_init'
,
function
()
{
register_widget
(
'SchoolPress_Note_Widget'
);
}
);
?>
Defining a Widget Area
name
Sidebar name (defaults to
Sidebar#
, where#
is the ID of the sidebar)id
Sidebar ID—must be all in lowercase, with no spaces (default is a numeric auto-incremented ID)
description
Text description of what/where the sidebar is; shown on widget management screen since 2.9 (default:
empty
)class
CSS class name to assign to the widget HTML (default:
empty
)before_widget
HTML to place before every widget (default:
<li id="%1$s" class="widget %2$s">
); usessprintf
for variable substitutionafter_widget
HTML to place after every widget (default:
</li>\n
)before_title
HTML to place before every title (default:
<h2 class="widgettitle">
)after_title
HTML to place after every title (default:
</h2>\n
)
register_sidebar
(
array
(
'name'
=>
'Assignment Pages Sidebar'
,
'id'
=>
'schoolpress_assignment_pages'
,
'description'
=>
'Sidebar used on assignment pages.'
,
'before_widget'
=>
''
,
'after_widget'
=>
''
,
'before_title'
=>
''
,
'after_title'
=>
''
));
if
(
!
dynamic_sidebar
(
'schoolpress_student_status'
))
{
//fallback code in case my_widget_area sidebar was not found
}
<?
php
//from twenty-thirteen/sidebar.php
if
(
is_active_sidebar
(
'sidebar-2'
)
)
:
?>
<div id="tertiary" class="sidebar-container" role="complementary">
<div class="sidebar-inner">
<div class="widget-area">
<?php
dynamic_sidebar
(
'sidebar-2'
);
?>
</div><!-- .widget-area -->
</div><!-- .sidebar-inner -->
</div><!-- #tertiary -->
<?php
endif
;
?>
Embedding a Widget Outside of a Dynamic Sidebar
$widget
The PHP class name for your widget
$instance
An array containing the settings for your widget
$args
An array containing the arguments normally passed to
register_sidebar()
//show note widget, overriding global note
the_widget
(
'SchoolPress_Note_Widget'
,
//classname
array
(
'note'
=>
''
),
//instance vars
array
(
//widget vars
'before_widget'
=>
''
,
'after_widget'
=>
''
,
'before_title'
=>
''
,
'after_title'
=>
''
)
);
Dashboard Widgets API
Removing Dashboard Widgets
// From the Dashboard Widgets API Codex Page
// Main column:
$wp_meta_boxes
[
'dashboard'
][
'normal'
][
'high'
][
'dashboard_browser_nag'
]
$wp_meta_boxes
[
'dashboard'
][
'normal'
][
'core'
][
'dashboard_right_now'
]
$wp_meta_boxes
[
'dashboard'
][
'normal'
][
'core'
][
'dashboard_recent_comments'
]
$wp_meta_boxes
[
'dashboard'
][
'normal'
][
'core'
][
'dashboard_incoming_links'
]
$wp_meta_boxes
[
'dashboard'
][
'normal'
][
'core'
][
'dashboard_plugins'
]
// Side Column:
$wp_meta_boxes
[
'dashboard'
][
'side'
][
'core'
][
'dashboard_quick_press'
]
$wp_meta_boxes
[
'dashboard'
][
'side'
][
'core'
][
'dashboard_recent_drafts'
]
$wp_meta_boxes
[
'dashboard'
][
'side'
][
'core'
][
'dashboard_primary'
]
$wp_meta_boxes
[
'dashboard'
][
'side'
][
'core'
][
'dashboard_secondary'
]
$id
The ID defined when the meta box was added. This is set as the
id
attribute of the<div>
element created for the meta box.$page
The name of the administrator page the meta box was added to. Use
dashboard
to remove dashboard meta boxes.$context
Either
normal
,advanced
, orside
, depending on where the meta box was added.
// Remove all default WordPress dashboard widgets
function
sp_remove_dashboard_widgets
()
{
remove_meta_box
(
'dashboard_browser_nag'
,
'dashboard'
,
'normal'
);
remove_meta_box
(
'dashboard_right_now'
,
'dashboard'
,
'normal'
);
remove_meta_box
(
'dashboard_recent_comments'
,
'dashboard'
,
'normal'
);
remove_meta_box
(
'dashboard_incoming_links'
,
'dashboard'
,
'normal'
);
remove_meta_box
(
'dashboard_plugins'
,
'dashboard'
,
'normal'
);
remove_meta_box
(
'dashboard_quick_press'
,
'dashboard'
,
'side'
);
remove_meta_box
(
'dashboard_recent_drafts'
,
'dashboard'
,
'side'
);
remove_meta_box
(
'dashboard_primary'
,
'dashboard'
,
'side'
);
remove_meta_box
(
'dashboard_secondary'
,
'dashboard'
,
'side'
);
}
add_action
(
'wp_dashboard_setup'
,
'sp_remove_dashboard_widgets'
);
//Remove network dashboard widgets
function
sp_remove_network_dashboard_widgets
()
{
remove_meta_box
(
'network_dashboard_right_now'
,
'dashboard-network'
,
'normal'
);
remove_meta_box
(
'dashboard_plugins'
,
'dashboard-network'
,
'normal'
);
remove_meta_box
(
'dashboard_primary'
,
'dashboard-network'
,
'side'
);
remove_meta_box
(
'dashboard_secondary'
,
'dashboard-network'
,
'side'
);
}
add_action
(
'wp_network_dashboard_setup'
,
'sp_remove_network_dashboard_widgets'
);
Adding Your Own Dashboard Widget
$widget_id
An ID for your widgets that is added as a CSS class name to the wrapper for the widget and also used as the array key for the dashboard widgets array.
$widget_name
Name of the widget displayed in the widget heading.
$callback
Callback function that renders the widget.
$control_callback
Optional. Defaults to
null
. Callback function to handle the display and processing of a configuration page for the widget.
Example 7-2. Assignments dashboard widget
<?
php
/*
Add dashboard widgets
*/
function
sp_add_dashboard_widgets
()
{
wp_add_dashboard_widget
(
'schoolpress_assignments'
,
'Assignments'
,
'sp_assignments_dashboard_widget'
,
'sp_assignments_dashboard_widget_configuration'
);
}
add_action
(
'wp_dashboard_setup'
,
'sp_add_dashboard_widgets'
);
/*
Assignments dashboard widget
*/
//widget
function
sp_assignments_dashboard_widget
()
{
$options
=
get_option
(
"assignments_dashboard_widget_options"
,
array
()
);
if
(
!
empty
(
$options
[
'course_id'
]
)
)
{
$group
=
groups_get_group
(
array
(
'group_id'
=>
$options
[
'course_id'
]
)
);
}
if
(
!
empty
(
$group
)
)
{
echo
"Showing assignments for class "
.
$group
->
name
.
".<br />..."
;
/*
get assignments for this group and list their status
*/
}
else
{
echo
"Showing all assignments.<br />..."
;
/*
get all assignments and list their status
*/
}
}
//configuration
function
sp_assignments_dashboard_widget_configuration
()
{
//get old settings or default to empty array
$options
=
get_option
(
"assignments_dashboard_widget_options"
,
array
()
);
//saving options?
if
(
isset
(
$_POST
[
'assignments_dashboard_options_save'
]
)
)
{
//get course_id
$options
[
'course_id'
]
=
intval
(
$_POST
[
'assignments_dashboard_course_id'
]
);
//save it
update_option
(
"assignments_dashboard_widget_options"
,
$options
);
}
//show options form
$groups
=
groups_get_groups
(
array
(
'orderby'
=>
'name'
,
'order'
=>
'ASC'
)
);
?>
<p>Choose a class/group to show assignments from.</p>
<div class="feature_post_class_wrap">
<label>Class</label>
<select name="assignments_dashboard_course_id">
<option value=""
<?php
selected
(
$options
[
'course_id'
],
""
);
?>
>
All Classes
</option>
<?php
$groups
=
groups_get_groups
(
array
(
'orderby'
=>
'name'
,
'order'
=>
'ASC'
)
);
if
(
!
empty
(
$groups
)
&&
!
empty
(
$groups
[
'groups'
]
)
)
{
foreach
(
$groups
[
'groups'
]
as
$group
)
{
?>
<option value="
<?php
echo
intval
(
$group
->
id
);
?>
"
<?php
selected
(
$options
[
'course_id'
],
$group
->
id
);
?>
>
<?php
echo
$group
->
name
;
?>
</option>
<?php
}
}
?>
</select>
</div>
<input type="hidden" name="assignments_dashboard_options_save" value="1" />
<?php
}
?>
Settings API
Do You Really Need a Settings Page?
global
$schoolpress_settings
;
$schoolpress_settings
=
array
(
'info_email'
=>
This email address is being protected from spambots. You need JavaScript enabled to view it.'
,
'info_email_name'
=>
'SchoolPress'
);
This plugin will not be distributed outside my team.
The only people changing these settings are developers.
These settings will not need to be different across our different environments.
These settings are likely to change before release.
Could You Use a Hook or Filter Instead?
//don't require any caps by default, but allow developers to add checks
$caps
=
apply_filters
(
'wpdoc_caps'
,
array
());
if
(
!
empty
(
$caps
))
{
//guilty until proven innocent
$hascap
=
false
;
//must be logged in to have any caps at all
if
(
is_user_logged_in
())
{
//make sure the current user has one of the caps
foreach
(
$caps
as
$cap
)
{
if
(
current_user_can
(
$cap
))
{
$hascap
=
true
;
break
;
//stop checking
}
}
}
if
(
!
$hascap
)
{
//don't show them the file
header
(
'HTTP/1.1 503 Service Unavailable'
,
true
,
503
);
echo
"HTTP/1.1 503 Service Unavailable"
;
exit
;
}
}
//require any user account
add_filter
(
'wpdoc_caps'
,
function
(
$caps
)
{
return
array
(
'read'
);
});
//require admin account
add_filter
(
'wpdoc_caps'
,
function
(
$caps
)
{
return
array
(
'manage_options'
);
});
//authors only or users with a custom capability (doc)
add_filter
(
'wpdoc_caps'
,
function
(
$caps
)
{
return
array
(
'edit_post'
,
'doc'
);
});
Note
Only a small number of people will want to change this setting.
The people changing this setting are likely to be developers.
The people changing this setting are likely to have custom needs.
This setting would require a large number of individual settings or more complicated UI.
Use Standards When Adding Settings
Ignore Standards When Adding Settings
Add your menu sections and items per the standards, even if your settings pages themselves use a custom layout.
Remember to sanitize your inputs and use nonces when appropriate.
Use hooks and filters to whenever possible, if you’d like to allow others to extend your settings.
Use the same HTML elements and CSS classes whenever possible so the general style remains consistent with the rest of WordPress now and through future updates.
Paid Memberships Pro (whose code is posted on GitHub)
WooCommerce (whose code is posted on GitHub)
Rewrite API
# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress
Adding Rewrite Rules
$rule
A regular expression to match against the URL, just like you would use in an Apache rewrite rule.
$rewrite
The URL to rewrite to if the rule is matched. Matched groups from the rule regular expressions are contained in an array called
$matches
.$position
Specifies whether to place the rules above the default WordPress rules (top) or below them (bottom).
add_rewrite_rule
(
'/contact/([^/]+)/?'
,
'index.php?name=contact&subject='
.
$matches
[
1
],
'top'
);
add_rewrite_rule
(
flush_rewrite_rules
();
Flushing Rewrite Rules
Add the rule during plugin activation and immediately flush the rewrite rules using the
flush_rewrite_rules()
function.Add the rule during the
init
hook in case the rules are flushed manually through the Permalinks Settings page of the dashboard or by another plugin.Add a call to
flush_rewrite_rules()
during deactivation so the rule is removed on deactivation.
//Add rule and flush on activation.
function
sp_activation
()
{
add_rewrite_rule
(
'/contact/([^/]+)/?'
,
'index.php?name=contact&subject='
.
$matches
[
1
],
'top'
);
flush_rewrite_rules
();
}
register_activation_hook
(
__FILE__
,
'sp_activation'
);
/*
Add rule on init in case another plugin flushes,
but don't flush cause it's expensive
*/
function
sp_init
()
{
add_rewrite_rule
(
'/contact/([^/]+)/?'
,
'index.php?name=contact&subject='
.
$matches
[
1
],
'top'
);
}
add_action
(
'init'
,
'sp_init'
);
//Flush rewrite rules on deactivation to remove our rule.
function
sp_deactivation
()
{
flush_rewrite_rules
();
}
register_deactivation_hook
(
__FILE__
,
'sp_deactivation'
);
Other Rewrite Functions
add_rewrite_tag()
Another way to add custom querystring variables.
add_feed()
Add a new kind of feed to function like the RSS and ATOM feeds.
add_rewrite_endpoint
Add querystring variables to the end of a URL.
$name*
Name of the endpoint—for example,
'doc'
.$places*
Specifies which pages to add the endpoint rule to. Uses the
EP_*
constants defined in wp-includes/rewrite.php.
Example 7-3. The WP DOC plugin
<?
php
/*
Plugin Name: WP DOC
Plugin URI: http://bwawwp.com/wp-docx/
Description: Add /doc/ to the end of a page or post to download a .docx version.
Version: .1
Author: Stranger Studios
*/
/*
Register Rewrite Endpoint
*/
//Add /doc/ endpoint on activation.
function
wpdoc_activation
()
{
add_rewrite_endpoint
(
'doc'
,
EP_PERMALINK
|
EP_PAGES
);
flush_rewrite_rules
();
}
register_activation_hook
(
__FILE__
,
'wpdoc_activation'
);
//and init in case another plugin flushes, but don't flush cause it's expensive
function
wpdoc_init
()
{
add_rewrite_endpoint
(
'doc'
,
EP_PERMALINK
|
EP_PAGES
);
}
add_action
(
'init'
,
'wpdoc_init'
);
//flush rewrite rules on deactivation to remove our endpoint
function
wpdoc_deactivation
()
{
flush_rewrite_rules
();
}
register_deactivation_hook
(
__FILE__
,
'wpdoc_deactivation'
);
/*
Detect /doc/ use and return a .doc file.
*/
function
wpdoc_template_redirect
()
{
global
$wp_query
;
if
(
isset
(
$wp_query
->
query_vars
[
'doc'
]))
{
global
$post
;
//double check this is a post
if
(
empty
(
$post
->
ID
))
return
;
//headers for MS Word
header
(
"Content-type: application/vnd.ms-word"
);
header
(
'Content-Disposition: attachment;Filename='
.
$post
->
post_name
.
'.doc'
);
//html
?>
<html>
<body>
<h1>
<?php
echo
$post
->
post_title
;
?>
</h1>
<?php
echo
apply_filters
(
'the_content'
,
$post
->
post_content
);
?>
</body>
</html>
<?php
exit
;
}
}
add_action
(
'template_redirect'
,
'wpdoc_template_redirect'
);
?>
EP_NONE EP_PERMALINK EP_ATTACHMENT EP_DATE EP_YEAR EP_MONTH EP_DAY EP_ROOT EP_COMMENTS EP_SEARCH EP_CATEGORIES EP_TAGS EP_AUTHORS EP_PAGES EP_ALL
WP-Cron
Schedule the cron event. This will fire a specific hook/action at the defined interval.
Hook a function to that action.
Place the code you actually want to run within the callback function.
//schedule crons on plugin activation
function
sp_activation
()
{
//do_action('sp_daily_cron'); will fire daily
wp_schedule_event
(
time
(),
'daily'
,
'sp_daily_cron'
);
}
register_activation_hook
(
__FILE__
,
'sp_activation'
);
//clear our crons on plugin deactivation
function
sp_deactivation
()
{
wp_clear_scheduled_hook
(
'sp_daily_cron'
);
}
register_deactivation_hook
(
__FILE__
,
'sp_deactivation'
);
//function to run daily
function
sp_daily_cron
()
{
//do this daily
}
add_action
(
"sp_daily_cron"
,
"sp_daily_cron"
);
$timestamp
Timestamp for first time to run the hook. You can typically set it to
time()
.$recurrence
How often the event should run. You can pass
hourly
,daily
, ortwicedaily
, or use thecron_schedules
hook to add other intervals.$hook
The name of the action to fire on each recurrence.
$args
Any arguments you’d like to pass along to the hook fired can be added to the end of the
wp_schedule_event()
call.
Adding Custom Intervals
//add a monthly interval to use in cron jobs
function
sp_cron_schedules
(
$schedules
)
{
$schedules
[
'monthly'
]
=
array
(
'interval'
=>
60
*
60
*
24
*
30
,
//really 30 days
'display'
=>
'Once a Month'
);
}
add_filter
(
'cron_schedules'
,
'sp_cron_schedules'
);
//run on Mondays
function
sp_monday_cron
()
{
//get day of the week, 0-6, starting with Sunday
$weekday
=
date
(
"w"
);
//is it Monday?
if
(
$weekday
==
"1"
)
{
//execute this code on Mondays
}
}
add_action
(
"sp_daily_cron"
,
"sp_monday_cron"
);
Scheduling Single Events
Kicking Off Cron Jobs from the Server
define
(
'DISABLE_WP_CRON'
,
true
);
0 0 * * * wget -O - -q -t 1 http://yoursite.com/wp-cron.php?doing_wp_cron=1
Tip
Caution
Using Server Crons Only
Note
//run on Mondays
function
sp_monday_cron
()
{
//check that cron param was passed in
if
(
empty
(
$_REQUEST
[
'sp_cron_monday'
]))
return
false
;
//execute this code on Mondays
}
add_action
(
"init"
,
"sp_monday_cron"
);
0 0 * * 1 wget -O - -q -t 1 http://yoursite.com/?sp_cron_monday=1
Note
WP Mail
wp_mail
(
$to
,
$subject
,
$message
,
$headers
,
$attachments
)
$to
A single email address, comma-separated list of email addresses, or array of email addresses the email will be sent to (using the “To:” field).
$subject
The subject of the email.
$message
The body of the email. By default, the email is sent as a plain-text message and should not include HTML. However, if you change the content type (see the following example), you should include HTML in your message.
$headers
Optional array of mail headers to send with the message. This can be used to add CCs, BCCs, and other advanced mail headers.
$attachments
A single filename or array of filenames to be attached to the outgoing email.
The
wp_mail()
function is hookable. Thewp_mail
filter will pass an array of all of the parameters passed into thewp_mail()
function for you to filter. You can also filter the sending address using thewp_mail_from
andwp_mail_from_name
filters.The
wp_mail()
function can be passed a single filename or array of filenames in the$attachments
parameters, which will be attached to the outgoing email. Attaching files to emails is very complicated, butwp_mail()
makes it easy by wrapping around thePHPMailer
class, which itself wraps around the default PHPmail()
function.
Sending Nicer Emails with WordPress
//Update from email and name
function
sp_wp_mail_from
(
$from_email
)
{
return
This email address is being protected from spambots. You need JavaScript enabled to view it.'
;
}
function
sp_wp_mail_from_name
(
$from_name
)
{
return
'SchoolPress'
;
}
add_filter
(
'wp_mail_from'
,
'sp_wp_mail_from'
);
add_filter
(
'wp_mail_from_name'
,
'sp_wp_mail_from_name'
);
//send HTML emails instead of plain text
function
sp_wp_mail_content_type
(
$content_type
)
{
if
(
$content_type
==
'text/plain'
)
{
$content_type
=
'text/html'
;
}
return
$content_type
;
}
add_filter
(
'wp_mail_content_type'
,
'sp_wp_mail_content_type'
);
//add a header and footer from files in the active theme
function
sp_wp_mail_header_footer
(
)
{
//get header
$headerfile
=
get_stylesheet_directory
()
.
"email_header.html"
;
if
(
file_exists
(
$headerfile
))
$header
=
file_get_contents
(
$headerfile
);
else
$header
=
""
;
//get footer
$footerfile
=
get_stylesheet_directory
()
.
"email_footer.html"
;
if
(
file_exists
(
$footerfile
))
$footer
=
file_get_contents
(
$footerfile
);
else
$footer
=
""
;
//update message
[
'message'
]
=
$header
.
[
'message'
]
.
$footer
;
return
;
}
add_filter
(
'wp_mail'
,
'sp_wp_mail_header_footer'
);
File Header API
/*
Plugin Name: Paid Memberships Pro
Plugin URI: http://www.paidmembershipspro.com
Description: Plugin to Handle Memberships
Version: 1.7.3.2
Author: Stranger Studios
Author URI: http://www.strangerstudios.com
*/
get_plugin_data
(
$plugin_file
,
$markup
=
true
,
$translate
=
true
)
$plugin_file
The absolute path to the main plugin file where the header will be parsed.
$markup
A flag that, if set to
true
, will apply HTML markup to some of the header values. For example, the plugin URI will be turned into a link.$translate
A flag that, if set to
true
, will translate the header values using the current locale and text domain.
//must include this file
require_once
(
ABSPATH
.
"wp-admin/includes/plugin.php"
);
//remember current directory
$cwd
=
getcwd
();
//switch to themes directory
$plugins_dir
=
ABSPATH
.
"wp-content/plugins"
;
chdir
(
$plugins_dir
);
echo
"<pre>"
;
//loop through theme directories and print theme info
foreach
(
glob
(
"*"
,
GLOB_ONLYDIR
)
as
$dir
)
{
$plugin
=
get_plugin_data
(
$plugins_dir
.
"/"
.
$dir
.
"/"
.
$dir
.
".php"
,
false
,
false
);
print_r
(
$plugin
);
}
echo
"</pre>"
;
//switch back to current directory just in case
chdir
(
$cwd
);
wp_get_theme
(
$stylesheet
,
$theme_root
)
+
$stylesheet
The name of the directory for the theme. If not set, this parameter will be the current theme’s directory.
$theme_root
The absolute path to the theme’s root folder. If not set, the value returned by
get_raw_theme_root()
is used.
//remember current directory
$cwd
=
getcwd
();
//switch to themes directory
$themes_dir
=
dirname
(
get_template_directory
());
chdir
(
$themes_dir
);
echo
"<pre>"
;
//loop through theme directories and print theme info
foreach
(
glob
(
"*"
,
GLOB_ONLYDIR
)
as
$dir
)
{
$theme
=
wp_get_theme
(
$dir
);
print_r
(
$theme
);
}
echo
"</pre>"
;
//switch back to current directory just in case
chdir
(
$cwd
);
Adding File Headers to Your Own Files
$file
The full path and filename of the file to pull data from.
$default_headers
An array of the header fields to look for. The keys of the array should be the header names, and the values of the array should be regex expressions for parsing the label that comes before the “:” in the comment. You can usually just enter the header name as the regex as well.
$context
A label to differentiate between different kinds of headers. This parameter determines which
extra_{context}_headers
filter is applied to the default headers passed in://set headers for our files
$default_headers
=
array
(
"Title"
=>
"Title"
,
"Slug"
=>
"Slug"
,
"Version"
=>
"Version"
);
//remember current directory
$cwd
=
getcwd
();
//change to reports directory
$reports_dir
=
dirname
(
__FILE__
)
.
"/reports"
;
chdir
(
$reports_dir
);
echo
"<pre>"
;
//loop through .php files in reports directory
foreach
(
glob
(
"*.php"
)
as
$filename
)
{
$data
=
get_file_data
(
$filename
,
$default_headers
,
"report"
);
print_r
(
$data
);
}
echo
"</pre>"
;
//change back to the current directory in case someone expects the default
chdir
(
$cwd
);
Adding New Headers to Plugins and Themes
Example 7-4. The Stop Plugin Updates plugin
<?
php
/*
Plugin Name: Stop Plugin Updates
Plugin URI: http://bwawwp.com/plugins/stop-plugin-updates/
Description: "Allow Updates: No" i a plugin's header keeps it from updating.
Version: .1
Author: Stranger Studios
Author URI: http://www.strangerstudios.com
*/
//add AllowUpdates header to plugin
function
spu_extra_plugin_headers
(
$headers
)
{
$headers
[
'AllowUpdates'
]
=
"Allow Updates"
;
return
$headers
;
}
add_filter
(
"extra_plugin_headers"
,
"spu_extra_plugin_headers"
);
/*
loop through plugins
check if updates are disallowed and if so remove it from list
*/
function
spu_pre_set_site_transient_update_plugins
(
$update_plugins
)
{
//see if there are any plugins needing updates
if
(
!
empty
(
$update_plugins
)
&&
!
empty
(
$update_plugins
->
response
)
)
{
//loop through plugins
$new_plugins
=
array
();
foreach
(
$update_plugins
->
response
as
$pluginpath
=>
$plugin
)
{
//check if the plugin is allowed or not
$plugin_data
=
ABSPATH
.
'/wp-content/plugins/'
.
$pluginpath
;
$plugin_data
=
get_plugin_data
(
$plugin_data
);
if
(
strtolower
(
$plugin_data
[
'Allow Updates'
]
)
==
"no"
||
strtolower
(
$plugin_data
[
'Allow Updates'
]
)
==
"false"
)
{
//change checked version and don't add to the new response
$update_plugins
->
checked
[
$pluginpath
]
=
$plugin_data
[
'Version'
];
}
else
{
//not blocked. add plugin to new response
$new_plugins
[
$pluginpath
]
=
$plugin
;
}
}
$update_plugins
->
response
=
$new_plugins
;
}
return
$update_plugins
;
}
add_action
(
'pre_set_site_transient_update_plugins'
,
'spu_pre_set_site_transient_update_plugins'
);
?>
Heartbeat API
Note
Sending data to the server from the client via jQuery. This is done with the
heartbeat-send
event.Processing the request on the server and providing a response via PHP. This is done with the
heartbeat_received
filter.Processing the response on the client via jQuery. This is done with the
heartbeat-tick
event.
Make a folder in your plugin directory called the-teacher-life-saver.
Make a file in this folder called the-teacher-life-saver.php.
Review and copy the following code.
Save and activate the plugin, then test it on the administrator dashboard.
Example 7-5. The Teacher Life Saver plugin
<?
php
/**
* Plugin Name: The Teacher Life Saver
* Plugin URI: https://schoolpress.me/
* Description: Alert teachers of parent arrivals
* Version: 0.0.1
*/
// Action hook for creating a dashboard widget
function
ttls_dashboard_widget
()
{
global
$wp_meta_boxes
;
// widget ID, widget name, callback function
wp_add_dashboard_widget
(
'ttls_widget'
,
'Student Pick Ups'
,
'ttls_dashboard'
);
}
add_action
(
'wp_dashboard_setup'
,
'ttls_dashboard_widget'
);
// Markup for the dashboard widget
function
ttls_dashboard
()
{
echo
"<div id='ttls_message'></div>"
;
}
// Change the default pulse to every 15 seconds so we don't have to wait as long
function
ttls_heartbeat_settings
(
$settings
)
{
$settings
[
'interval'
]
=
15
;
// Anything between 15-60 seconds
return
$settings
;
}
add_filter
(
'heartbeat_settings'
,
'ttls_heartbeat_settings'
,
1
);
// Enqueue heartbeat.js and our JavaScript functions
function
ttls_heartbeat_init
()
{
// Only run this on the dashboard page (index.php)
global
$pagenow
;
if
(
$pagenow
!=
'index.php'
)
return
;
// Enqueue the Heartbeat API
wp_enqueue_script
(
'heartbeat'
);
// Load our JavaScript functions in the footer
add_action
(
"admin_footer"
,
"ttls_js_wp_footer"
);
}
add_action
(
"admin_init"
,
"ttls_heartbeat_init"
);
// JavaScript functions ran in the footer
function
ttls_js_wp_footer
()
{
?>
<script>
jQuery(document).ready(function() {
// Use heartbeat-send to send any keys/values in the data array
jQuery(document).on('heartbeat-send', function(e, data) {
data['client'] = 'check-for-parents';
});
// Use heartbeat-tick function check for data and take action
jQuery(document).on('heartbeat-tick', function(e, data) {
if(data['server'])
document.getElementById("ttls_message").innerHTML = data['server']
+ document.getElementById("ttls_message").innerHTML;
});
});
</script>
<?php
}
// Server-side function to receive and process the request then return a response
function
ttls_heartbeat_received
(
$response
,
$data
)
{
// Look for whatever data was passed from JS heartbeat-send function
if
(
$data
[
'client'
]
==
'check-for-parents'
)
{
// Build whatever response you want
$r
=
'<p>'
;
$r
.=
date
(
'm/j/y g:i a'
,
current_time
(
'timestamp'
,
0
)
);
$r
.=
" - Nina Messenlehner's father Brian Messenlehner "
;
$r
.=
"has arrived in a 2007 White Hummer H2."
;
$r
.=
'</p>'
;
return
$r
;
}
add_filter
(
'heartbeat_received'
,
'ttls_heartbeat_received'
,
10
,
2
);
?>