Wordpress
Install and maintain
Composer
"extra": {
"webroot-dir": "www",
"webroot-package": "wordpress",
"installer-paths": {
"app/plugins/{$name}/": ["type:wordpress-plugin"],
"app/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
"app/themes/{$name}/": ["type:wordpress-theme"]
}
}
<?php
/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
define('ABSPATH', dirname(__FILE__) . '/wordpress/');
/** Absolute path to the WordPress wp-content directory, which holds your themes, plugins, and uploads */
//define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/wp-content' );
?>
<?php
include_once './vendor/autoload.php';
require( './wordpress/wp-blog-header.php' );
?>
Docker
Config
<?php
/*
Automatic Url + Content Dir/Url Detection for Wordpress
Based on https://gist.github.com/CMCDragonkai/7578784#gistcomment-1237365 and https://stackoverflow.com/questions/1175096/how-to-find-out-if-youre-using-https-without-serverhttps
*/
// Use realpath to resolve symlinks (like /homez.xxx/ to /home/)
$document_root = rtrim(str_replace(array('/', '\\'), '/', realpath($_SERVER['DOCUMENT_ROOT'])), '/');
$root_dir = str_replace(array('/', '\\'), '/', __DIR__);
$wp_dir = str_replace(array('/', '\\'), '/', rtrim(ABSPATH, '/'));
$wp_content_dir = str_replace(array('/', '\\'), '/', WP_CONTENT_DIR);
$root_url = substr_replace($root_dir, '', stripos($root_dir, $document_root), strlen($document_root));
$wp_url = substr_replace($wp_dir, '', stripos($wp_dir, $document_root), strlen($document_root));
$wp_content_url = substr_replace($wp_content_dir, '', stripos($wp_content_dir, $document_root), strlen($document_root));
$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' || isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443 || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') ? 'https://' : 'http://';
$host = rtrim($_SERVER['SERVER_NAME'], '/');
$port = (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != '80' && $_SERVER['SERVER_PORT'] != '443') ? ':' . $_SERVER['SERVER_PORT'] : '';
$root_url = $scheme . $host . $port . $root_url;
$wp_url = $scheme . $host . $port . $wp_url;
$wp_content_url = $scheme . $host . $port . $wp_content_url;
define('WP_HOME', $root_url); //url to index.php
define('WP_SITEURL', $wp_url); //url to wordpress installation
define('WP_CONTENT_URL', $wp_content_url); //wp-content url
?>
Update site URL
SET @current = 'http://some.example.com';
SET @next = 'https://www.example.com';
UPDATE wp_options SET option_value = replace(option_value, @curent, @next) WHERE option_name = 'home' OR option_name = 'siteurl';
UPDATE wp_posts SET post_content = replace(post_content, @curent, @next);
UPDATE wp_postmeta SET meta_value = replace(meta_value,@curent,@next);
UPDATE wp_usermeta SET meta_value = replace(meta_value, @curent,@next);
UPDATE wp_links SET link_url = replace(link_url, @curent,@next);
UPDATE wp_comments SET comment_content = replace(comment_content , @curent,@next);
# For images inside posts
UPDATE wp_posts SET post_content = replace(post_content, @curent, @next);
# For images linked in old link manager
UPDATE wp_links SET link_image = replace(link_image, @curent, @next);
# For images linked as attachments
UPDATE wp_posts SET guid = replace(guid, @curent, @next);
# Serialized data via serialize() or json_encode() aren't touched, see https://deliciousbrains.com/wp-migrate-db-pro/doc/serialized-data/
WP Migrate DB โ WordPress plugin | WordPress.org - Free version handle metadata PHP serialization trouble
Plugins
Jekyll Exporter โ WordPress plugin | WordPress.org - "One-click WordPress plugin that converts all posts, pages, taxonomies, metadata, and settings to Markdown and YAML which can be dropped into Jekyll (or Hugo or any other Markdown and YAML based site engine)"
Rest API
Themes
Simple/cleared theme: BlankSlate โ Free WordPress Themes
<?php
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wlwmanifest_link');
remove_action('wp_head', 'wp_shortlink_wp_head');
remove_action('wp_head', 'wp_generator');
remove_action('wp_head', array( $sitepress, 'meta_generator_tag', 20 ) );
add_filter('xmlrpc_enabled', '__return_false');
add_filter('json_enabled', '__return_false');
add_filter('json_jsonp_enabled', '__return_false');
?>
https://github.com/WordPress/WordPress/blob/master/wp-content/themes/twentyfourteen
Block theme
Aka theme.json
Template hierarchy
Theme development
Nav menu
Aka navigation menu
<?php
// .sub-menu
add_filter( 'nav_menu_submenu_css_class', function ( $classes, $args, $depth ) {
if($args->theme_location == 'primary') $classes = array($depth == 0 ? 'ns-main-nav-subsections' : 'ns-main-nav-items');
return $classes;
}, 10, 3);
// .menu-item > a
add_filter( 'nav_menu_link_attributes', function ( $atts, $item, $args, $depth ) {
if($args->theme_location == 'primary') $atts['class'] = $depth == 0 ? 'ns-main-nav-section-link' : 'ns-main-nav-link';
return $atts;
}, 10, 4);
// .menu-item
add_filter( 'nav_menu_css_class', function ( $classes, $item, $args, $depth ) {
if($args->theme_location == 'primary'){
switch ($depth){
case 0:
$class = 'ns-main-nav-section';
break;
case 1:
$class = 'ns-main-nav-subsection';
break;
default:
$class = 'ns-main-nav-item';
break;
}
$classes = array($class);
}
return $classes;
}, 10, 4);
// Add wrapper to .sub-menu
//class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
// public function start_lvl( &$output, $depth = 0, $args = null ) {
// if($depth == 0) $output .= '<div class="mcp-main-nav-subsections-wrapper">';
// parent::start_lvl($output, $depth, $args);
// }
// public function end_lvl( &$output, $depth = 0, $args = null ) {
// parent::end_lvl($output, $depth, $args);
// if($depth == 0) $output .= '</div>';
// }
//}
// Render the nav menu
wp_nav_menu(
array(
'container' => '',
'menu_class' => 'ns-main-nav-sections',
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
'theme_location' => 'primary',
// 'walker' => new Custom_Walker_Nav_Menu(),
)
);
?>
Performance and optimisation
Use OPcache
Tools:
P3 (Plugin Performance Profiler) โ WordPress Plugins - Works only with PHP โค 5
Plugins:
SAVEQUERIES
should be false
Custom fields
Fields starts with _
are not visible defaut meta box for custom field
Gutenberg editor plugins can be written with JSX/ES6 or ES5 (without compilation)
Validation / sanitization / authorization:
sanitize_callback
andauth_callback
arguments are depreciated
Some:
See also:
Custom post types
Custom plugins
mu-plugins aka multi user plugins, must-use plugins
Taxonomy
Aka categories, tags
Rewrite
call
flush_rewrite_rules()
Settings > Permalinks > Save changes
Regenerate thumbnails
Responsive images
Update URL and media filename
Backup and migration
AJAX
http://code.tutsplus.com/articles/getting-started-with-ajax-wordpress-pagination--wp-23099
http://codex.wordpress.org/AJAX_in_Plugins
http://www.smashingmagazine.com/2011/10/18/how-to-use-ajax-in-wordpress/
https://wordpress.org/plugins/rest-api/
Captcha
http://wordpress.org/plugins/really-simple-captcha/
Form
WordPress Forms - Gravity Forms Contact Form Builder and Lead Data Management Plugin For WordPress - emailing + Gravity Flow - Automate your business processes with Gravity Forms. (request and approval process)
User feedback
http://wordpress.org/plugins/get-satisfaction-for-wordpress/
http://wordpress.org/plugins/user-voice/
http://www.onfry.com/projects/voteitup/
https://getsatisfaction.com/getsatisfaction/topics/pulling_back_support_for_wordpress_integration
https://developer.uservoice.com/docs/api/getting-started/
http://feedback.uservoice.com/forums/1-general-feedback
http://wordpress.org/plugins/uservoice-idea-list-widget/
http://feedback.uservoice.com/knowledgebase/articles/56243-use-a-link-custom-trigger-to-open-the-uservoice
http://feedback.uservoice.com/knowledgebase/articles/276635-use-just-smartvote-the-contact-form-or-satisfacti
GetSatisfaction
Zendesk
Desk
Job manager
http://wordpress.org/plugins/job-manager/
http://wordpress.org/plugins/wp-job-manager/
http://premium.wpmudev.org/blog/6-wordpress-job-board-solutions/
Social integration Twitter / Facebook
http://wordpress.org/plugins/facebook/
https://wordpress.org/plugins/twitter/
http://wordpress.org/plugins/recent-facebook-posts/
http://wordpress.org/plugins/custom-facebook-feed/
http://wordpress.org/plugins/fb-wallpost-widget/screenshots/
FB as RSS
Multilingual
Note: Has a performance impact
WPML - quite slow and could break things (remove translations when save, etc.)
Localization
Aka translation
Use full locale name in .mo
filenames For mu-plugins (see load_muplugin_textdomain()
):
<?php
// In wp-content/mu-plugins/<pluginslug>/<pluginslug>.php (with a "loader" `wp-content/mu-plugins/<somename>.php`: `require WPMU_PLUGIN_DIR . '/<pluginslug>/<pluginslug>.php';`)
add_action('muplugins_loaded', fn() => load_muplugin_textdomain('textdomain', dirname(plugin_basename(__FILE__)) . '/languages'));
// This will try to load `<WP_LANG_DIR>/plugins/<textdomain>-<locale>.mo` else `wp-content/mu-plugins/<pluginslug>/languages/<textdomain>-<locale>.mo`
// Where `WP_LANG_DIR` is usually `wp-content/languages`
?>
For plugins (see load_plugin_textdomain()
):
<?php
// In wp-content/plugins/<pluginslug>/<pluginslug>.php
add_action('init', fn() => load_plugin_textdomain('textdomain', false, dirname(plugin_basename(__FILE__)) . '/languages'));
// This will try to load `<WP_LANG_DIR>/plugins/<textdomain>-<locale>.mo` else `wp-content/plugins/<pluginslug>/languages/<textdomain>-<locale>.mo`
// Where `WP_LANG_DIR` is usually `wp-content/languages`
?>
For theme (see load_theme_textdomain()
):
<?php
// In wp-content/themes/<themeslug>/functions.php
add_action('after_setup_theme', fn() => load_theme_textdomain('textdomain', get_template_directory() . '/languages'));
// This will try to load `<WP_LANG_DIR>/themes/<textdomain>-<locale>.mo` else `wp-content/themes/<themeslug>/languages/<locale>.mo`
// Where `WP_LANG_DIR` is usually `wp-content/languages`
?>
See also _get_plugin_data_markup_translate(), for a plugin with meta comment Domain Path: /languages
(default to empty) and Text Domain: text-domain
(require, or no text domain will be (auto-)loaded) will load wp-content/plugin/<pluginslug><domainpath>/<textdomain>-<locale>.mo
.
To debug which .mo
files are loaded:
<?php
$debuginfo_textdomain_mofiles = [];
add_filter('load_textdomain_mofile', function ($mofile, $domain) {
global $debug_textdomain_mofiles;
$is_mofile_readable = is_readable($mofile);
$mo = new MO();
$e = new Exception();
$debug_textdomain_mofiles[] = array(
'filename' => $mofile,
'domain' => $domain,
'readable' => $is_mofile_readable,
'trace' => $e->getTraceAsString(),
'importable' => $is_mofile_readable && $mo->import_from_file($mofile)
);
return $mofile;
}, 10, 2);
// Then later in page:
//var_dump($debuginfo_textdomain_mofiles);
?>
<?php
printf(
/* translators: %s: Price amount. */
__('Price: %s', 'my-text-domain'),
get_product_price()
);
?>
wp i18n make-pot | WP-CLI Command | WordPress Developer Resources - "Create a POT file for a WordPress project."
Plurals
<?php
/* translators: %d: number of people. */
printf( _n( '%s person', '%s people', $count, 'text-domain' ), number_format_i18n( $count ) );
/* translators: 1: WordPress version number, 2: number of bugs. */
printf( _n( '<strong>Version %1$s</strong> addressed %2$d bug.', '<strong>Version %1$s</strong> addressed %2$d bugs.', $bugs_count, 'text-domain' ), $wp_version, $bugs_count );
?>
SVG
// In functions.php
function add_svg_to_upload_mimes( $upload_mimes ) {
$upload_mimes['svg'] = 'image/svg+xml';
$upload_mimes['svgz'] = 'image/svg+xml';
return $upload_mimes;
}
add_filter( 'upload_mimes', 'add_svg_to_upload_mimes', 10, 1 );
Improving SVG Display in the Media Library WordPress SVG Support: How to Enable SVGs in WordPress
Security
See Wordpress
File permissions for automatic update
Apache use a different group than FTP users. It's a safe measure, but updates can't be automatic (require SSH or FTP credentials). Ex: FTP: user 522/538; Apache/PHP: 48/48 (www-data)
#!/bin/bash
USER=user
PASSWD=password
SITE=www.example.com
#DIR_MOD=0755
DIR_MOD=0775
#FILE_MOD=0644
FILE_MOD=0664
ROOT_DIR=/public_html
# use `cat` instead of last command after the last pipe for dry-run
{
lftp <<EOF
open -u $USER,$PASSWD $SITE
find $ROOT_DIR
exit
EOF
} | gawk 'BEGIN { print "open -u '$USER','$PASSWD' '$SITE'" } { if (match($0 ,/\/$/)) printf "chmod '$DIR_MOD' \"%s\"\n", $0; else printf "chmod '$FILE_MOD' \"%s\"\n", $0 } END { print "exit" }' | lftp
The Code is in
get_filesystem_method()
. Wordpress tries to create a file'wp-content/temp-write-test-'.time()
define( 'FTP_USER', 'username' );
define( 'FTP_PASS', 'password' );
define( 'FTP_HOST', 'ftp.example.org' );
Minify
Autoptimize
Merge + Minify + Refresh (doesnโt include an option to optimize your HTML)
Fast Velocity Minify
Test
From westonruter/amp-wp-theme-compat-analysis/start.sh
:
#!/bin/bash
set -x
set -e
lando start
if [ ! -e public ]; then
mkdir public
fi
if [ ! -e public/index.php ]; then
lando wp core download
fi
if ! lando wp core is-installed; then
lando wp core install --url="https://amp-wp-theme-compat-analysis.lndo.site/" --title="AMP WP Theme Compatibility Analysis" --admin_user=admin --admin_password=password --admin_email=nobody@example.com
fi
lando wp plugin install --activate amp
lando wp option update --json amp-options '{"theme_support":"standard"}'
lando wp plugin install --activate wordpress-importer
lando wp plugin install --activate block-unit-test
#lando wp plugin install --activate coblocks
if [ ! -e themeunittestdata.wordpress.xml ]; then
wget https://raw.githubusercontent.com/WPTRT/theme-unit-test/master/themeunittestdata.wordpress.xml
fi
if [[ 0 == $(lando wp menu list --format=count) ]]; then
lando wp import --authors=create themeunittestdata.wordpress.xml
fi
if [[ 0 == $(lando wp post list --post_type=attachment --post_name=accelerated-mobile-pages-is-now-just-amp --format=count) ]]; then
wget https://blog.amp.dev/wp-content/uploads/2019/04/only_amp.mp4
lando wp media import --title="Accelerated Mobile Pages is now just AMP" only_amp.mp4
rm only_amp.mp4
fi
lando wp create-monster-post
lando wp populate-initial-widgets
bash check-wporg-themes.sh 100
OOP
In wp-content/themes/<themeslug>/functions.php
:
<?php
class My_Theme
{
public function __construct()
{
add_action('init', fn() => $this->init());
//or add_action('init', Closure::fromCallable([$this, 'init']));
}
private function init()
{
// ...
}
}
$my_theme = new My_Theme();
?>
Code
External script use WordPress functions
Aka headless, api
<?php
// Hide PHP Notice, Warning, logs...
ini_set('display_errors', 0);
header('Content-Type: application/json');
const WP_USE_THEMES = false;
$parse_uri = explode( 'wp-content', $_SERVER['SCRIPT_FILENAME'] );
require_once( $parse_uri[0] . 'wp-load.php' );
?>
Last updated
Was this helpful?