Nasrul Hazim Bin Mohamad

WordPress: Requirement for Deployment to Production

Nov
29

WordPress Plugins

  1. DuplicatorCreate a backup of your WordPress files and database. Duplicate and move an entire site from one location to another in a few steps. Create a full snapshot of your site at any point in time.
  2. Better Search and Replace A small plugin for running a search/replace on your WordPress database.

Linux Commands

# Find something in a file
# This will be useful when you need to find hardcoded IP/domain in files
grep -rnw '/path/to/somewhere/' -e "pattern"

Recommended requirement for WordPress is PHP 5.6, MySQL 5.5 and make sure to enable Apache mod_rewrite module.

Apache: Required Modules

  • mod_authz_host
  • log_config_module
  • expires_module
  • deflate_module
  • headers_module
  • setenvif_module
  • mime_module
  • autoindex_module
  • dir_module
  • alias_module
  • rewrite_module
  • negotiation_module
  • ssl_module
  • php5_module

PHP: Required Modules

  • cURL
  • Date/Time
  • DOM
  • POSIX Regex
  • Filter
  • FTP
  • GD
  • Hash
  • Iconv
  • JSON
  • libxml
  • Multibyte String
  • MySQL
  • OpenSSL
  • PCRE
  • SimpleXML
  • Sockets
  • SPL
  • Tokenizer
  • XML Parser
  • XMLReader
  • Zlib
  • Mcrypt

Increase Upload Limit & Post Limit in .htaccess

# Set Upload Limit
php_value post_max_size 25M
php_value memory_limit 128M
php_value upload_max_filesize 25M

# Suhosin - if Suhosin installed
php_value suhosin.post.max_vars 20000
php_value suhosin.request.max_vars 20000

Hardening

# Changing directory permissions recursively
find /var/www/html -type d -exec chmod 755 {} \;

# Changing file permissions recursively
find /var/www/html -type f -exec chmod 644 {} \;

.htaccess

# Prevent direct access to wp-login.php
<Files wp-login.php>
order deny,allow
Deny from all
</Files>

# Prevent people from browsing the content of directories
Options All -Indexes

# Protect the .htaccess file itself
<Files .htaccess>
order allow,deny
deny from all
</Files>

# Securing wp-includes
# Block the include-only files. Place this outside of WordPress block to prevent overwrite
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>

# Securing wp-config.php
<files wp-config.php>
order allow,deny
deny from all
</files>

wp-config.php

# wp-config.php
define('DISALLOW_FILE_EDIT', true);

# Turn Off PHP Error Reporting
error_reporting(0);
@ini_set(‘display_errors’, 0);

WordPress Plugin – All In One WP Security & Firewall

References:

  1. WordPress Requirement
  2. WordPress Hardening
  3. Keeping WordPress Secure the Ultimate Guide
  4. What are PHP Extensions and Libraries WordPress Needs
  5. PHP Security Best Practice

WPML: Custom Language Switcher

Oct
06

Quick reference on how to add custom language switcher in WPML


if(!function_exists('icl_post_languages')) {
	function icl_post_languages(){
	  $languages = icl_get_languages('skip_missing=1');
          echo '<pre>';
          var_dump($languages);
          echo '</pre>';
	  if(1 < count($languages)){
              // iterate through the list to create your custom language switcher
	  }
	}
}
/*
array(2) 
{ 
	["ms"]=> array(12) 
	{ 
		["code"]=> string(2) "ms" 
		["id"]=> string(2) "64" 
		["native_name"]=> string(6) "Melayu" 
		["major"]=> string(1) "0" 
		["active"]=> string(1) "1" 
		["default_locale"]=> string(5) "ms_MY" 
		["encode_url"]=> string(1) "0" 
		["tag"]=> string(5) "ms-MY" 
		["translated_name"]=> string(6) "Melayu" 
		["url"]=> string(26) "http://localhost/" 
		["country_flag_url"]=> string(88) "http://localhost/wp-content/plugins/sitepress-multilingual-cms/res/flags/ms.png" 
		["language_code"]=> string(2) "ms" 
	} 
	["en"]=> array(12) 
	{ 
		["code"]=> string(2) "en" 
		["id"]=> string(1) "1" 
		["native_name"]=> string(7) "English" 
		["major"]=> string(1) "1" 
		["active"]=> int(0) 
		["default_locale"]=> string(5) "en_US" 
		["encode_url"]=> string(1) "0" 
		["tag"]=> string(5) "en-US" 
		["translated_name"]=> string(7) "English" 
		["url"]=> string(29) "http://localhost/en/" 
		["country_flag_url"]=> string(88) "http://localhost/wp-content/plugins/sitepress-multilingual-cms/res/flags/en.png" 
		["language_code"]=> string(2) "en" 
	} 
}
*/

Reference: Custom Language Switcher

WordPress: Message Class

Sep
20

A simple class for displaying admin messages – error, notice and successful.


class Message {
	public static function error($message) {
		return '<div class="error"><p>'.$message.'</p></div>';
	}

	public static function success($message) {
		return '<div class="updated"><p>'.$message.'</p></div>';
	}

	public static function notice($message) {
		return '<div class="update-nag">'.$message.'</div>';
	}
}

/** 
 * Usage
 **/
Message::error('Hutang 2.6Bil! Bayar!');
Message::notice('Kacing! (PM: eh..cam bunyi duit masuk je');
Message::success('PM: Hasil derma orang bagi. Syukur lahhhh..boleh byr hutang');
wp-admin-message-error

wp-admin-message-error

wp-admin-message-notice

wp-admin-message-notice

wp-admin-message-success

wp-admin-message-success

How to Include PHP ActiveRecord in WordPress as Global Feature

Sep
12

This post for developers who used to ORM/ActiveRecord and not familiar with WordPress Query (like me) and want the ORM/ActiveRecord to be available in WordPress.

As per post’s title, this post regarding how you can include PHP ActiveRecord for global use – let say you have multiple plugins that use PHP ActiveRecord, you can’t load same PHP ActiveRecord classes / redeclared classed in an application or system – to be precise in PHP.

So the solution is to add in PHP ActiveRecord in {wordpress_installation}/wp-includes

Configurations

  1. Download PHP ActiveRecord and extract to {wordpress_installation}/wp-includes
  2. Create a new file in {wordpress_installation}/wp-includes/php-activerecord/ called AR.php and use the codes below
    <?php
    
    class AR {
    	static $conn;
    	static $model_directories;
    
    	public static function add_model_directories($list) {
    		if(!is_array($list)) {
    			return false;
    		}
    
    		if(!is_array(self::$model_directories)) {
    			self::$model_directories = array();
    		}
    
    		foreach ($list as $key => $value) {
    			if(!in_array($value, self::$model_directories)) {
    				self::$model_directories[] = $value;
    			}
    		}
    
    		return true;
    	}
    }
    
    function ar_init() {
    	$connections = array(
    		'development' => 'mysql://'.DB_USER.':'.DB_PASSWORD.'@'.DB_HOST.'/'.DB_NAME,
    	);
    
    	AR::$conn = ActiveRecord\Config::instance();
    	AR::$conn->set_model_directory(AR::$model_directories);
    	AR::$conn->set_connections($connections);
    }
    
  3. Update ActiveRecord\Config class in php-activerecord/lib/Config.php for method set_model_directory and get_model_directory. This to ensure we can include multiple model directories from multiple plugins.:
    	public function set_model_directory($dir)
    	{
    		if( is_array($dir) ) {
    			foreach( $dir as $k => $path ) {
    				if( !file_exists($path ) ) {
    					throw new ConfigException('Invalid or non-existent model directory: '.$path);
    				}
    			}
    		} else if( $dir  && !file_exists( $dir) ) {
    			throw new ConfigException('Invalid or non-existent model directory: '. $dir);
    		}
    
    		$this->model_directory = $dir;
    	}
    
    	public function get_model_directory()
    	{
    		return $this->model_directory;
    	}
    
  4. Include ActiveRecord.php and AR.php in {wordpress_installation}/wp-settings.php after Line 155:
    require( ABSPATH . WPINC . '/php-activerecord/ActiveRecord.php' );
    require( ABSPATH . WPINC . '/php-activerecord/AR.php' );
    
  5. Add the following codes after Line 241 to initialize PHP ActiveRecord:
    /**
     * Initialize PHP ActiveRecord
     */
    ar_init();
    

How to Use in Your Plugin

  1. Create your WordPress Plugin (you can use wppb.me)
  2. Open up your WordPress Plugin main file and add in the following code in order to include your model directory in AR class:
    AR::add_model_directories(array(
    	PLUGIN_PATH . 'includes' . DIRECTORY_SEPARATOR . 'models'
    ));
    
  3. Now create a sample model file which extend ActiveRecord\Model in your plugin’s model directory. Following are the sample codes:
    <?php
    
    class GTState extends ActiveRecord\Model {
    	static $table_name = 'gt_states';
    }
    
  4. Now you’re ready to use the GTState class in your view / controller.

I hope this trick will speed up the development progress.

Sorry WordPress Developers, I need a little hack to develop fast with PHP ActiveRecord, because I’m a beginner with WordPress, more things to learn.

References

  1. Load Multiple Model Directories
  2. PHP ActiveRecord Wiki

WordPress: Custom RSS Feed

Aug
13

There’s three main steps to create your custom RSS Feed.

  1. Register your feed in functions.php

    add_action( 'after_setup_theme', 'custom_rss' );
    /**
     * Register custom RSS template.
     */
    function custom_rss() {
    	add_feed( 'custom', 'render_custom_css' );
    }
    /**
     * Custom RSS template callback.
     */
    function render_custom_css() {
    	get_template_part( 'feed', 'custom' );
    }
    
  2. Create your template file feed-custom.php in your theme folder.
    You can copy and paste from wp-includes/feed-rss2.php to make things easier and make changes something like the following – I fetch post with post_type is ajde_events and is it a featured event:

    <?php
    /**
     * RSS2 Feed Template for displaying RSS2 Posts feed.
     *
     * @package WordPress
     */
    
    header('Content-Type: ' . feed_content_type('rss-http') . '; charset=' . get_option('blog_charset'), true);
    $more = 1;
    
    echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>';
    
    /**
     * Fires between the xml and rss tags in a feed.
     *
     * @since 4.0.0
     *
     * @param string $context Type of feed. Possible values include 'rss2', 'rss2-comments',
     *                        'rdf', 'atom', and 'atom-comments'.
     */
    do_action( 'rss_tag_pre', 'rss2' );
    ?>
    <rss version="2.0"
    	xmlns:content="http://purl.org/rss/1.0/modules/content/"
    	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    	xmlns:dc="http://purl.org/dc/elements/1.1/"
    	xmlns:atom="http://www.w3.org/2005/Atom"
    	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    	<?php
    	/**
    	 * Fires at the end of the RSS root to add namespaces.
    	 *
    	 * @since 2.0.0
    	 */
    	do_action( 'rss2_ns' );
    	?>
    >
    
    <channel>
    	<title><?php bloginfo_rss('name'); wp_title_rss(); ?></title>
    	<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
    	<link><?php bloginfo_rss('url') ?></link>
    	<description><?php bloginfo_rss("description") ?></description>
    	<lastBuildDate><?php echo mysql2date('D, d M Y H:i:s +0000', get_lastpostmodified('GMT'), false); ?></lastBuildDate>
    	<language><?php bloginfo_rss( 'language' ); ?></language>
    	<sy:updatePeriod><?php
    		$duration = 'hourly';
    
    		/**
    		 * Filter how often to update the RSS feed.
    		 *
    		 * @since 2.1.0
    		 *
    		 * @param string $duration The update period. Accepts 'hourly', 'daily', 'weekly', 'monthly',
    		 *                         'yearly'. Default 'hourly'.
    		 */
    		echo apply_filters( 'rss_update_period', $duration );
    	?></sy:updatePeriod>
    	<sy:updateFrequency><?php
    		$frequency = '1';
    
    		/**
    		 * Filter the RSS update frequency.
    		 *
    		 * @since 2.1.0
    		 *
    		 * @param string $frequency An integer passed as a string representing the frequency
    		 *                          of RSS updates within the update period. Default '1'.
    		 */
    		echo apply_filters( 'rss_update_frequency', $frequency );
    	?></sy:updateFrequency>
    
    	<?php 
    	$args = array(
    			'meta_key' => '_featured',
    			'meta_value' => 'yes',
    			'post_type' => 'ajde_events',
    			'post_status' => 'publish'
    		);
    		$the_query = new WP_Query( $args );
    	?>
    	<?php do_action( 'rss2_head'); ?>
    	<?php if ( $the_query->have_posts() ) : ?>
    		<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
    			<item>
    				<title><?php the_title(); ?></title>
    				<link><?php the_permalink_rss() ?></link>
    				<comments><?php comments_link_feed(); ?></comments>
    				<pubDate><?php echo mysql2date('D, d M Y H:i:s +0800', get_post_time('Y-m-d H:i:s', true), false); ?></pubDate>
    				<dc:creator><![CDATA[<?php the_author() ?>]]></dc:creator>
    				<?php the_category_rss('rss2') ?>
    
    				<guid isPermaLink="false"><?php the_guid(); ?></guid>
    				<?php if (get_option('rss_use_excerpt')) : ?>
    						<description><![CDATA[<?php the_excerpt_rss(); ?>]]></description>
    				<?php else : ?>
    						<description><![CDATA[<?php the_excerpt_rss(); ?>]]></description>
    					<?php $content = get_the_content_feed('rss2'); ?>
    					<?php if ( strlen( $content ) > 0 ) : ?>
    						<content:encoded><![CDATA[<?php echo $content; ?>]]></content:encoded>
    					<?php else : ?>
    						<content:encoded><![CDATA[<?php the_excerpt_rss(); ?>]]></content:encoded>
    					<?php endif; ?>
    				<?php endif; ?>
    				<wfw:commentRss><?php echo esc_url( get_post_comments_feed_link(null, 'rss2') ); ?></wfw:commentRss>
    				<slash:comments><?php echo get_comments_number(); ?></slash:comments>
    				<?php rss_enclosure(); ?>
    				<?php do_action( 'rss2_item' ); ?>
    			</item>
    		<?php endwhile; ?>
    
    		<?php wp_reset_postdata(); ?>
    	<?php else : ?>
    		
    	<?php endif; ?>
    </channel>
    </rss>
    
  3. Basically you are done and you can go to your WordPress site http://yourblog.com?feed=custom. You may need add the following in order to flush your RSS Feed Cache – it’s a headache to wait for an hour to get your RSS Feed updated – change it to a second!
    /**
     * Setting a new cache time for feeds in WordPress
     */
    function prefix_set_feed_cache_time( $seconds ) {
    	return 1;
    }
    add_filter( 'wp_feed_cache_transient_lifetime' , 'prefix_set_feed_cache_time' );
    

P/S: You can customize your output by adding a bit of styling if you wanted to!

WordPress: Install from Composer

Jul
02

Create a composer.json in project root directory and copy paste the following composer configuration. Then run composer install.

{
    "name": "blog/your-name",
    "description": "my Official blog",
    "repositories": [
    {
      "type": "composer",
      "url": "http://wpackagist.org"
    }
    ],
    "require": {
        "php": ">=5.4",
        "johnpbloch/wordpress": "4.2"
    },
    "extra": {
        "wordpress-install-dir": "wp"
    }
}

Once done, copy wp/index.php to project root directory and update require( dirname( __FILE__ ) . '/wp-blog-header.php' ); to require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );.

Now run the WP at http://localhost/wp-project-root and continue with installation and you’re done.

References:

  1. Using Composer With WordPress
  2. Composer in WordPress
  3. Giving WordPress It’s Own Directory