Just another WordPress.com site

This article is a contribution by Charmlander. Recently Tested With

Basic installation

  1. First install WordPress
  2. Install pydio with Mysql database
  3. Check both Wp and pydio has admin login.(Same admin username is preferred)
  4. I do not know what is the exact problem but I had some problems with different admin usernames maybe some browser cache problems.

WordPress Side

  1. Login to admin panel in WordPress and install pydio_cms_4.0.1 via Plugins/Addnew option
  2. Be aware that the original file you downloaded from sourceforge.net has all the cms plugins.So extract that file and select the directory wordpress/ajaxplorer only.You can either zip it again and upload to wordpress or copy the whole directory (only AjaXplorer in the wordpress dir) to wp-content/plugins directory.Warning !!! Be sure that there is no recurring directories otherwise the plugin install will return with error.
  3. Activate the plugin.
  4. On the dashboard select Settings/AjaXplorer
  5. In the Ajaxplorer path enter the full installation path something like : /home/user/public_html/ajaxplorer
  6. Choose a secret key
  7. Be sure that Auto Create (Create Ajxp users when they login) option is Checked to Yes.And do not forget to save changes.
  8. We finished the WordPress side.Now it’s time for Pydio part.

Pydio Side

  1. After all these done login to your pydio with admin account and open Settings/Global Configurations/Core Configs/Authentication panel.
  2. On the panel goto Main Instance Section and do the followings;
    • Instance Type : Remote Authentication
    • CMS Type : WordPress
    • WordPress url : This is where your logged out users will be redirected to
    • Login uri : /wp-login.php
    • Exit action : Selecet either goto specific page(to wp url above) or only perform logout.
    • Local Prefix : Enter something like wpajax_ (or similar)
    • Roles Map : Leave it blank
    • Secret Key : Your wordpress plugin secret key
    • Users : AJXP_DATA_PATH/plugins/auth.serial/users.ser
    • Transmit Clear Pass : No
    • Auto Create Users : Yes
    • Login Redirect : Leave it blank
    • Admin Login : Leave it blank
  3. Now goto wordpress dashboard and activate register users in settings/general —>Membership select Anyone can register.So your register action is activated you have to install a plugin in wordpress in order to send confirmation (send password) e-mails to users being registered if you are on a shared hosting.In this scenario I use Easy WP SMTP.Install it and configure it for sending mails.
  4. After you had configured your Easy WP SMTP plugin in WordPress and allow users to register, I think the most tricky part comes against us.If you try to register an user right now you’d encounter with an error like below :
  5. « Fatal error: Cannot redeclare ajxp_gluecode_updateRole() (previously declared in /home/user/public_html/test/pydio/plugins/auth.remote/glueCode.php:79) in /home/user/public_html/test/pydio/plugins/auth.remote/glueCode.php on line 103″ To get rid of this notorious error message open your AjaXplorer plugin file located in wordpress/wp-content folder something like /home/user/public_html/wordpress/wp-content/plugins/ajaxplorer/class.WpAjxp.php
  6. On the line 36: add_action('user_register', array(&$this, 'createUser'), 1, 1);
  7. replace it with : add_action('action=register', array(&$this, 'createUser'), 1, 1);
  8. Now Pydio can hook the register actions within the WordPress installation.


After all changes are applied now you can login or register a new user in wordpress and maybe use a menu on homepage or redirect him automatically to pydio page after login.It depends on your WordPress knowledge beyond this section.All I have to say is when an user logged in WordPress, he can automatically login to Pydio without another login screen. And this is the test site where I installed while writing this tutorial.I didn’t tested it deeply but practised some login and register actions and it’s fulfilly working.If you want to try just register the site and on homepage click pydio menu on top. http://www.zonomo.com/test/wp P5.0.4 / WP3.6.1 Tutorial | Pydio, formerly AjaXplorer.


After a recent update of MAMP on my system, I was no longer able to load Magento sites on my localhost. The problem seems to be a PHP compatibilty issue and the solution is pretty simple.

Loading up Magento sites on my local development server, I found myself greeted with a page saying:

Can’t retrieve entity config: core/store_group, followed by a long trace: message and I found the solution online here http://ecarobar.com/magento-cant-retrieve-entity-config-solution/

UPDATE wp_posts SET guid = replace(guid, 'http://www.old-domain.com','http://www.new-domain.com');
UPDATE wp_posts SET post_content = replace(post_content, 'http://www.old-domain.com', 'http://www.new-domain.com');

WordPress Plugins

.main-inner .widget .post-body ul{ list-style: decimal; } .main-inner .widget .post-body ul li ul{ list-style: lower-roman outside none; }
Some Important WordPress plugin:

  • User Management

    • User Access Manager
    • Capability Manager Enhanced
    • Role Scoper (best combination to use with Capability Manager Enhanced)
  • Excerpt

    • Advanced Excerpt
  • Security

    • Better WP Security
    • Really Simple CAPTCHA
  • PDF

    • BSK PDF Manager
    • Print Friendly and PDF
  • Custom Post

  • Forms

  • Site Down/Maintenance

    • Maintenance Mode
  • Cross Platform/Cross CMS

    • Joomla/Mambo to WP Migrator
  • Social Media

    • Recent Tweets Widget
  • Slider/Carousel/Lightbox

Sometimes, you need to upload a large file to your site, and the default 8mb limit in WordPress isn’t enough. I attempted to change my limit directly on my server, then in the .htaccess file and then in wp-config.php but neither change worked for me. Then I came across this little snippet by Word Impressed which actually worked and allowed me to increase my max upload file size limit.

All you need to do is create a php.ini file and upload it into your /wp-admin folder. Then add the following:

memory_limit = 32M
upload_max_filesize = 32M
post_max_size = 32M
file_uploads = On

First WordPress plugin

First WordPress plugin

/* Plugin Name: crud operation
 * Plugin URI: http://www.datum.com
 * description: a plugin for performing basic crud operations
 * Vesion: 1.0
 * Author URI: http://www.datum.com
 * license: GP2
function crud_setUserCaps($param) {
    $editor = get_role(strtolower($param));
function crud_unsetUserCaps($param) {
    $editor = get_role(strtolower($param));
function set_caps() {
function crud_activationFun($param) {
    // Then flush them
register_activation_hook(__FILE__, ‘crud_activationFun’);
function crud_deactivationFun($param) {
register_deactivation_hook(__FILE__, ‘crud_deactivation_fun’);
add_action(‘wp_enqueue_scripts’, ‘crud_loadScripts’);
function crud_loadScripts($param) {
    wp_register_script(‘docready’, plugins_url(‘js/docready.js’, __FILE__));
add_action(‘wp_enques_styles’, ‘crud_loadStyles’);
function crud_loadStyles($param) {
    wp_register_style(‘baseStyle’, plugins_url(‘css/style.css’, __FILE__));
add_action(‘init’, ‘crud_addRewriteRules’);
function crud_addRewriteRules() {
    add_rewrite_rule(“download_items/^([^/]*)/?”, ‘index.php?post_type=download_items=$matches[1]’, ‘top’);
add_action(‘init’, ‘crud_registerDownloadItems’);
function crud_registerDownloadItems() {
    $labels = array(
        ‘menu_name’ => _x(‘download_items’, ‘Download Items’),
        ‘name’ => ‘Download Items’,
        ‘singular_name’ => ‘Download Item’,
        ‘add_new’ => ‘Add New’,
        ‘add_new_item’ => ‘Add New Download Item’,
        ‘edit_item’ => ‘Edit Download Item’,
        ‘new_item’ => ‘New Download Item’,
        ‘all_items’ => ‘All Download Items’,
        ‘view_item’ => ‘View Download Item’,
        ‘search_items’ => ‘Search Download Items’,
        ‘not_found’ => ‘No Download Items found’,
        ‘not_found_in_trash’ => ‘No Download Items found in Trash’,
        ‘parent_item_colon’ => ”,
        ‘menu_name’ => ‘Download Items’
    $args = array(
        ‘labels’ => $labels,
        ‘hierarchical’ => true,
        ‘description’ => ‘Slideshows’,
        ‘supports’ => array(‘title’, ‘editor’),
        ‘public’ => true,
        ‘show_ui’ => true,
        ‘show_in_menu’ => true,
        ‘show_in_nav_menus’ => true,
        ‘publicly_queryable’ => true,
        ‘exclude_from_search’ => false,
        ‘has_archive’ => true,
        ‘query_var’ => true,
        ‘can_export’ => true,
        ‘rewrite’ => array(
            ‘slug’ => ‘download_items’,
            ‘with_front’ => false,
            ‘feed’ => true,
            ‘pages’ => true
        ‘capabilities’ => array(
            // Meta capabilities
            ‘edit_post’ => ‘edit_download_item’,
            ‘read_post’ => ‘read_download_item’,
            ‘delete_post’ => ‘delete_download_item’,
            // Primitive capabilities
            ‘edit_posts’ => ‘edit_download_items’,
            ‘delete_post’ => ‘delete_download_items’,
            ‘read_posts’ => ‘read_download_items’,
        ‘capability_type’ => ‘post’
    register_post_type(‘download_items’, $args);

add_action(‘add_meta_boxes’, ‘crud_addMetaBoxes’);
function crud_addMetaBoxes() {
    //add_meta_box($id, $title, $callback, $page, $context, $priority);
    add_meta_box(“crud_custom_fields”, “Other Information”, ‘crud_viewFields’, “download_items”, “normal”);
function crud_viewFields() {
    global $post, $wpdb;
    $table_name = $wpdb->prefix . “categories”;
    $checked_status = ”;
    $download_price = get_post_meta($post->ID, ‘download_price’);
    $crud_category = get_post_meta($post->ID, ‘crud_category’);
    $myrows = $wpdb->get_results( “SELECT category_name, category_Label FROM {$table_name}” );
    $html = ”;
    $html .= ‘

    $html .= ”









Download price
Categories “;
    $html .= ‘

        foreach ($myrows as $key => $value) {
            if (stristr($crud_category[0], $value->category_name) == true) {
                $checked_status = ‘ checked=”checked” ‘;
                $html .= ‘

  •                     ‘ . $value->category_Label . ‘
  • ‘;
            } else {
                $html .= ‘

  •                     ‘ . $value->category_Label . ‘
  • ‘;

        $html .= “

    echo $html;
/* Save Slider Options to database */
add_action(‘save_post’, ‘crud_saveDownloadInfo’);
function crud_saveDownloadInfo($post_id) {

    // verify nonce
    if (!wp_verify_nonce($_POST[‘crud_custom_fields_nonce’], basename(__FILE__))) {
        return $post_id;
    // check autosave
    if (defined(‘DOING_AUTOSAVE’) && DOING_AUTOSAVE) {
        return $post_id;
    // check permissions
    if (‘download_items’ == $_POST[‘post_type’] && current_user_can(‘edit_download_items’, $post_id)) {
        $download_price = (isset($_POST[‘crud_download_price’]) ? $_POST[‘crud_download_price’] : ”);
        $categories = (isset($_POST[‘crud_category’]) ? $_POST[‘crud_category’] : ”);
        $categories = strip_tags(json_encode($categories));
        update_post_meta($post_id, “download_price”, $download_price);
        update_post_meta($post_id, “crud_category”, $categories);
    } else {
        return $post_id;
// Callback function to show fields in meta box
add_filter(‘map_meta_cap’, ‘crud_downloadItemMetaCap’, 10, 4);
function crud_downloadItemMetaCap($primitive_caps, $meta_cap, $user_id, $args) {
    // If meta-capability is not download_item based do nothing.
    if (!in_array($meta_cap, array(‘edit_download_item’, ‘delete_download_item’, ‘read_download_item’))) {
        return $primitive_caps;
    // Check post is of post type.
    $post = get_post($args[0]);
    $post_type = get_post_type_object($post->post_type);
    if (‘download_items’ != $post_type) {
        return $primitive_caps;
    $primitive_caps = array();
    switch ($meta_cap):
        case ‘edit_download_item’:
            if ($post->post_author == $user_id) {
                // User is post author
                if (‘publish’ == $post->post_status) {
                    // download_item is published: require ‘edit_published_download_items’ capability
                    $primitive_caps[] = $post_type->cap->edit_published_posts;
                } elseif (‘trash’ == $post->post_status) {
                    if (‘publish’ == get_post_meta($post->ID, ‘_wp_trash_meta_status’, true)) {
                        // download_item is a trashed published post require ‘edit_published_download_items’ capability
                        $primitive_caps[] = $post_type->cap->edit_published_posts;
                } else {
                    $primitive_caps[] = $post_type->cap->edit_posts;
            } else {
                // The user is trying to edit a post belonging to someone else.
                $primitive_caps[] = $post_type->cap->edit_others_posts;
                // If the post is published or private, extra caps are required.
                if (‘publish’ == $post->post_status) {
                    $primitive_caps[] = $post_type->cap->edit_published_posts;
                } elseif (‘private’ == $post->post_status) {
                    $primitive_caps[] = $post_type->cap->edit_private_posts;
        case ‘read_download_item’:
            if (‘private’ != $post->post_status) {
                // If the post is not private, just require read capability
                $primitive_caps[] = $post_type->cap->read;
            } elseif ($post->post_author == $user_id) {
                // Post is private, but current user is author
                $primitive_caps[] = $post_type->cap->read;
            } else {
                // Post is private, and current user is not the author
                $primitive_caps[] = $post_type->cap->read_private_post;
        case ‘delete_download_item’:
            if ($post->post_author == $user_id) {
                // Current user is author, require delete_download_items capability
                $primitive_caps[] = $post_type->cap->delete_posts;
            } else {
                // Current user is no the author, require delete_others_download_items capability
                $primitive_caps[] = $post_type->cap->delete_others_posts;
            // If post is published, require delete_published_posts capability too
            if (‘publish’ == $post->post_status) {
                $primitive_caps[] = $post_type->cap->delete_published_posts;
    return $primitive_caps;
function crud_frontendDeleteLink() {
    if (!current_user_can(‘delete_download_items’)) {
        echo ‘    ‘;
    $url = add_query_arg(
                ‘action’ => ‘frontend_delete’,
                ‘post’ => get_the_ID()
    echo “Delete“;
if (isset($_REQUEST[‘action’]) && $_REQUEST[‘action’] == ‘frontend_delete’) {
    add_action(‘init’, ‘crud_FrontendDeletePost’);
function crud_FrontendDeletePost() {
    if (!current_user_can(‘delete_download_items’))
    $post_id = (isset($_REQUEST[‘post’]) ? (int) $_REQUEST[‘post’] : 0);
    if (empty($post_id))
add_shortcode(‘downloadItem’, crud_downloadItemFun);
function crud_downloadItemFun() {
    global $post ;
   $paged = (get_query_var(‘paged’)) ? get_query_var(‘paged’) : 1;
    $args = array(
      ‘posts_per_page’ => 10,
      ‘post_type’ => ‘download_items’,
      ‘paged’ => $paged
    $newsObj = new WP_Query($args);
    if ($newsObj->have_posts()):    ?>








                while ($newsObj->have_posts()): $newsObj->the_post();
        $curr_id = get_the_ID();
        $meta = get_post_meta( $curr_id,’crud_category’ );
        $meta = preg_replace(‘/[^a-zA-Z0-9\,]/’, ”, $meta);
        $download_price = get_post_meta( $curr_id,’download_price’ );








Title Categories Price Edit Delete
0 ) {echo $meta[0] ;} ?> 0 ) echo implode(‘,’, $download_price); ?> “>Edit

function crud_pluginSetting() {
    add_menu_page(‘Crud Plugin Settings’, ‘Crud Settings’, ‘administrator’, ‘crud_settings’, ‘crud_settingFunction’);
if (isset($_REQUEST[‘category_label’]) && (!empty ($_REQUEST[‘category_label’]) || ” != ‘frontend_delete’)) {
    crud_installData($_REQUEST[‘category_name’], $_REQUEST[‘category_label’]);
function crud_settingFunction() {
    $category_label = (get_option(‘category_label’) != ”) ? get_option(‘category_label’) : ‘category_label100’;
    $category_name = (get_option(‘category_name’) != ”) ? get_option(‘category_name’) : ‘category_name100’;
    $html = ‘



Select Your Settings

        ‘ . wp_nonce_field(‘update-options’) . ‘










                        Category Label
                        Category Name



    echo $html;
function crud_createDBTable() {
    global $wpdb;
    $table_name = $wpdb->prefix . "categories";
    $sql = "CREATE TABLE $table_name (
  id mediumint(9) NOT NULL AUTO_INCREMENT,
  category_name text NOT NULL,
  category_label text NOT NULL,
  UNIQUE KEY id (id)
    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
function crud_dropTable() {
    global $wpdb;
    $table_name = $wpdb->prefix . "categories";
    $sql = "DROP TABLE $table_name";
function crud_installData($category_name = "News",$category_Label = "News") {
   global $wpdb;
   $table_name = $wpdb->prefix . "categories";
   //$category_name = "News";
   //$category_Label = "News";
   $rows_affected = $wpdb->insert( $table_name, array( 'category_name' => $category_name, 'category_Label' => $category_Label) );
//Debug Errors if there are any
function save_error(){
    file_put_contents(ABSPATH. 'wp-content/uploads/2013/error_activation.html', ob_get_contents());

PHP N-tier: Introduction N-Tier Strategy
PHP N-Tier Strategy Step By Step Tutorial – Part 1: May be you face complicated situation in your client’s architecture. They use many technology for solve their complicated business process. If it happen, n-tier may be possible one solution you can choice. It this post, we begin to understanding possible N-Tier architecture.
General principle that we must consider if wants to build N-tier application:
  • Every layer must be independent physically. It doesn’t mean every layer have to exist in separated computer. But, every layer can be distributed every where (separated computer or not).
  • Each layer must transfer information only to/from previous/next layer.
  • You can change technology used in every layer without change entire system. Example, – you want to change database layer- from mySql to PostgreSQL.
Following sample architecture design use 5-tier:
  • Presentation GUI, do parse HTML, XHTML, WML.
  • Presentation Logic do rendering process HTML, XHTML to send using HTTP to browser. It accepts data from business logic and tie to HTML. This process run at php at web server.
  • Business Logic, manipulate and transform data. Simple, task of this layer is fetch data from data access tier and prepare before send to presentation logic. This process run at server that utilize XML.
  • Data access tier have task to connect and retrieve data from database.
  • Data tier is aplication database such as mySQL, PostGreSQL, and others.

PHP N-tier: Possible n-tier Application Use PHP

PHP N-Tier Strategy Step By Step Tutorial – Part 2: In this post, we will try to design n-tier application use PHP. This is my 5-tier design:
  • Presentation GUI: Using smarty template to generate HTML.
  • Presentation Logic: PHP.
  • Business Logic: Use NuSOAP.
  • Data Access Tier: Using ADOdb.
  • Data tier: Using mySQL.
We ever talked about nusoap, adodb, and mysql. If you still understand about them, please read posts about them.

PHP N-tier: Database Layer Using MySQL

PHP N-Tier Strategy Step By Step Tutorial – Part 3: For practice, we will build simple application to show 5-tier design. We will begin from database layer. At the database layer, we use MySQL.
Create a database, for example “test”. Then create table. This is the query:

“CREATE TABLE `books` (

Next, try to insert sample data, such as:

“INSERT INTO `test`.`books` (
`id` ,
`title` ,
NULL , ‘PHP Undercover’, ‘Wiwit Siswoutomo’
), (
NULL , ‘PHP Enterprise’, ‘Wiwit Siswoutomo’

Ok, next post we will try how to connect database use PHP ADOdb as data access tier.

PHP N-tier: Data Access Tier Using PHP ADOdb

PHP N-Tier Strategy Step By Step Tutorial – Part 4: In this post, we will build data access tier. We use PHP Adodb. About PHP Adodb, you can read at here.
For this practice, create a folder named “ntier” within www/test. Place your all PHP ADOdb folder at here. Thus, you have www/test/ntier/adodb.
Then create a file named “dataaccess.php” within www/test/ntier. Enter following code

 $databasetype = ‘mysql’;
$server = ‘localhost’;
$user   = ‘root’;
$password = ‘admin’;
$database = ‘test’;
 $db = ADONewConnection($databasetype);
$db->debug = false;
$db->Connect($server, $user, $password, $database);
 $recordSet = &$db->Execute(‘select * from books’);
 if (!$recordSet)
  print $conn->ErrorMsg();
  while (!$recordSet->EOF) {
    print $recordSet->fields[0].’ ‘.$recordSet->fields[1].’ ‘.$recordSet->fields[2].’
 $recordSet->Close(); # optional

Test it. Open your browser. point to http://localhost/test/ntier/dataacces.php.

PHP N-tier: Building Business Logic

PHP N-Tier Strategy Step By Step Tutorial – Part 5: After create connection to database at this post, we will process this data. We prepare this data in order to can to send using webservices. We named this: business logic layer.
This practice only work well for php 4. In php 5, we will talk next serial post. Because there is embedded function in php 5. First, copy library of nusoap within www/test/ntier/lib. If you don’t understant to place nusoap, please read this post.
Open again file dataacces.php within www/test/ntier. Update like following:

// call library
require_once ( ‘./lib/nusoap.php’ ); //nusoap
require_once (‘adodb/adodb.inc.php’);//adodb
 // create instance
$server = new soap_server();
 // initialize WSDL support
$server->configureWSDL( ‘hello’ , ‘urn:hellowsdl’ );
 // place schema at namespace with prefix tns
$server->wsdl->schemaTargetNamespace = ‘urn:hellowsdl’;
 // register method
$server->register(‘books’,  // method name
array(), // input parameter
‘urn:hellowsdl’ , // namespace
‘urn:hellowsdl#hello’, // soapaction
‘rpc’, // style
‘encoded’, // use
‘Load list of book to the caller’ // documentation
 // configuration database
  // method
function books(){
  $db = ADONewConnection(__databasetype__);
  $db->debug = false;
  $db->Connect(__server__, __user__, __password__, __database__);
   $recordSet = &$db->Execute(‘select * from books’);
   if (!$recordSet){
        return new soap_fault(‘Server’,”,$conn->ErrorMsg());
  } else {
    while (!$recordSet->EOF) {
          $books[] = array(‘id’=>$recordSet->fields[0],
     $recordSet->Close();       #optional    

  // return value to client
  $obj = new soapval(‘return’,’array’,$books);
  return $obj->serialize();

Then, test it. Open your browser. Point to http://localhost/test/ntier/dataacces.php. You will get like this:

PHP N-tier: Building Presentation Logic

PHP N-Tier Strategy Step By Step Tutorial – Part 6: After create business logic, now we try to catch the data. We called this: presentation logic layer. We still use nusoap to do this job.
Just for practice, we will create client same folder/location with server function. Create a file named “client_book.php” within www/test/ntier. Enter following code:

require_once (‘lib/nusoap.php’);
 $client = new soapclient(‘http://localhost/test/ntier/dataacces.php’);
 $response = $client->call(‘books’);
  echo “FAULT: Code: (“.$client->faultcode.”)
  echo “String: “.$client->faultstring;
  $r = $response[0];
  $count = count($r);












Id Title Author


Now, open your browser and point to http://localhost/test/ntier/client_book.php.

PHP N-tier: Building Presentation GUI

PHP N-Tier Strategy Step By Step Tutorial – Part 7: We have grabbed data from server at presentation logic. And we tie data to html at previous post. It will give us more flexible if we you template system. So, we can change any layout without change all. for this job, we use smarty template.
Exactly, what is smarty template? I rewrite from their readme: Smarty is a template engine for PHP. Many other template engines for PHP provide basic variable substitution and dynamic block functionality. Smarty takes a step further to be a “smart” template engine, adding features such as configuration files, template functions, and variable modifiers, and making all of this functionality as easy as possible to use for both programmers and template designers.
First, please download smarty template at smarty.php.net. Extract compressed file to www/test/ntier. You will get folder named like “smarty-2.6.xx”. Rename, for simply, become: “smarty”.
Then, create folders named “templates” and “templates_c” within www/test/ntier.
Open again your client_book.php, rewrite with this code:

// call library
require_once (‘./lib/nusoap.php’);
require_once (‘./smarty/libs/smarty.class.php’);
 // retrieve data from server
// use webservice by nusoap
$client = new soapclient(‘http://localhost:8048/test/ntier/dataacces.php&#8217;);
$response = $client->call(‘books’);
  echo “FAULT: Code: (“.$client->faultcode.”)
  echo “String: “.$client->faultstring;
  // start using smarty
  $smarty = new Smarty;

  // assign parameters
  $smarty->assign(“title”,”Books Collection”);

  //load template

Now, you can see. Your code be more clean. No html tag over there. All html tag will be place at template.
Create a file named “template.tpl” within www/test/ntier/templates. Then enter following code:








{foreach key=key item=item from=$books}

  {foreach key=key item=item from=$item}



Id Title Author

Congrat! you have understand how to build 5-tier use php. You can expand this tutorial as you need.
%d bloggers like this: