ShahiLanding

Developer Guide

This comprehensive guide covers extending ShahiLandin, custom integrations, and advanced development patterns for plugin developers.

Getting Started

Development Environment Setup

Requirements:

    1. Local WordPress installation (Local by Flywheel, XAMPP, or Docker)
    2. PHP 7.4+ with Xdebug (for debugging)
    3. Node.js 16+ and npm (for asset building)
    4. Composer (for dependency management)
    5. WP-CLI (for automation)
    6. Git (for version control)
    7. Recommended IDE:

    8. Visual Studio Code with PHP Intelephense extension
    9. PHPStorm
    10. Sublime Text with PHP packages
    11. Plugin Structure

      `
      ShahiLandin/
      ├── assets/ # Frontend assets
      │ ├── css/ # Stylesheets
      │ ├── js/ # JavaScript files
      │ └── images/ # Image assets
      ├── includes/ # Core PHP classes
      │ ├── admin/ # Admin-specific classes
      │ ├── api/ # REST API controllers
      │ ├── cli/ # WP-CLI commands
      │ ├── frontend/ # Frontend classes
      │ ├── modules/ # Feature modules
      │ ├── post-types/ # Custom post type definitions
      │ ├── services/ # Business logic services
      │ ├── class-plugin.php # Main plugin class
      │ ├── class-options.php # Options management
      │ └── helpers.php # Helper functions
      ├── templates/ # Template files
      ├── languages/ # Translation files
      ├── tests/ # PHPUnit tests
      ├── shahilandin.php # Plugin bootstrap file
      └── uninstall.php # Uninstall cleanup
      `

      Debugging

      Enable WordPress debug mode in wp-config.php:

      `php
      define(‘WP_DEBUG’, true);
      define(‘WPDEBUGLOG’, true);
      define(‘WPDEBUGDISPLAY’, false);
      define(‘SCRIPT_DEBUG’, true);
      `

      Log to file:
      `php
      errorlog(‘Debug message: ‘ . printr($data, true));
      `

      Use Xdebug breakpoints in your IDE for step-by-step debugging.

      Core Concepts

      Plugin Architecture

      ShahiLandin follows these architectural patterns:

    12. Namespace Organization: All classes use the ShahiLandin namespace
    13. Service Layer: Business logic in service classes (e.g., Landing_Service)
    14. Single Responsibility: Each class has one clear purpose
    15. Dependency Injection: Services receive dependencies via constructor
    16. Hook-Based: Extensible via WordPress actions and filters
    17. Custom Post Type

      Landing pages use the shahi_landing post type:

      `php
      use ShahiLandin\PostType\LandingPost_Type;

      // Get post type name
      $posttype = LandingPostType::POSTTYPE; // ‘shahi_landing’

      // Register additional meta
      add_action(‘init’, function() {
      registerpostmeta(‘shahilanding’, ‘custom_meta’, [
      ‘type’ => ‘string’,
      ‘single’ => true,
      ‘showinrest’ => true,
      ]);
      });
      `

      Services

      Services encapsulate business logic:

      `php
      use ShahiLandin\Services\Landing_Service;

      // Create landing page
      $postid = LandingService::create_landing([
      ‘post_title’ => ‘My Landing Page’,
      ‘post_status’ => ‘publish’,
      ‘html_content’ => ‘

      Content

      ‘,
      ‘css_content’ => ‘.custom { color: red; }’
      ]);

      // Get statistics
      $stats = LandingService::getstatistics($post_id);

      // Duplicate landing page
      $newid = LandingService::duplicatelanding($postid);
      `

      Meta Boxes

      Landing pages use custom meta boxes for HTML, CSS, and settings:

      `php
      use ShahiLandin\Admin\Meta_Boxes;

      // Meta keys
      $html = getpostmeta($postid, MetaBoxes::HTMLMETAKEY, true);
      $css = getpostmeta($postid, MetaBoxes::CSSMETAKEY, true);
      $rendermode = getpostmeta($postid, MetaBoxes::RENDERMODEMETAKEY, true);
      `

      Custom Integrations

      External Analytics Integration

      Integrate with third-party analytics platforms:

      `php
      MixpanelIntegration {
      private $token;

      public function __construct() {
      $this->token = getoption(‘shahilandinmixpanel_token’);

      if ($this->token) {
      addaction(‘shahilandinviewtracked’, [$this, ‘trackview’], 10, 2);
      addaction(‘shahilandinconversiontracked’, [$this, ‘trackconversion’], 10, 3);
      }
      }

      public function trackview($postid, $data) {
      $this->send_event(‘Landing Page Viewed’, [
      ‘Landing ID’ => $post_id,
      ‘Landing Title’ => getthetitle($post_id),
      ‘Referrer’ => $data[‘referrer’] ?? ”,
      ‘URL’ => getpermalink($postid)
      ]);
      }

      public function trackconversion($postid, $goal, $data) {
      $this->send_event(‘Landing Page Conversion’, [
      ‘Landing ID’ => $post_id,
      ‘Landing Title’ => getthetitle($post_id),
      ‘Goal’ => $goal,
      ‘Conversion Data’ => $data
      ]);
      }

      private function sendevent($eventname, $properties) {
      $event = [
      ‘event’ => $event_name,
      ‘properties’ => array_merge($properties, [
      ‘token’ => $this->token,
      ‘time’ => time()
      ])
      ];

      $url = ‘https://api.mixpanel.com/track/’;

      wpremotepost($url, [
      ‘body’ => [
      ‘data’ => base64encode(jsonencode($event))
      ],
      ‘timeout’ => 5
      ]);
      }
      }

      new ShahiLandinMixpanelIntegration();
      `

      CRM Integration

      Sync conversions with your CRM:

      `php
      HubSpotSync {
      private $api_key;

      public function __construct() {
      $this->apikey = getoption(‘hubspotapikey’);

      addaction(‘shahilandinconversiontracked’, [$this, ‘syncto_hubspot’], 10, 3);
      }

      public function synctohubspot($post_id, $goal, $data) {
      // Only sync email signups
      if ($goal !== ‘newsletter_signup’ || empty($data[’email’])) {
      return;
      }

      $contact_data = [
      ‘properties’ => [
      ’email’ => $data[’email’],
      ‘landingpageid’ => $post_id,
      ‘landingpagetitle’ => getthetitle($post_id),
      ‘signup_source’ => ‘shahilandin’,
      ‘signup_date’ => date(‘Y-m-d H:i:s’)
      ]
      ];

      $response = wpremotepost(‘https://api.hubapi.com/crm/v3/objects/contacts’, [
      ‘headers’ => [
      ‘Authorization’ => ‘Bearer ‘ . $this->api_key,
      ‘Content-Type’ => ‘application/json’
      ],
      ‘body’ => jsonencode($contactdata),
      ‘timeout’ => 10
      ]);

      if (iswperror($response)) {
      errorlog(‘HubSpot sync failed: ‘ . $response->geterror_message());
      }
      }
      }

      new ShahiLandinHubSpotSync();
      `

      Email Marketing Integration

      Connect to email marketing platforms:

      `php
      MailchimpIntegration {
      private $api_key;
      private $list_id;

      public function __construct() {
      $this->apikey = getoption(‘mailchimpapikey’);
      $this->listid = getoption(‘mailchimplistid’);

      addaction(‘shahilandinconversiontracked’, [$this, ‘addsubscriber’], 10, 3);
      }

      public function addsubscriber($postid, $goal, $data) {
      if ($goal !== ‘newsletter_signup’ || empty($data[’email’])) {
      return;
      }

      $dc = substr($this->apikey, strpos($this->apikey, ‘-‘) + 1);
      $url = “https://{$dc}.api.mailchimp.com/3.0/lists/{$this->list_id}/members/”;

      $member_data = [
      ’email_address’ => $data[’email’],
      ‘status’ => ‘subscribed’,
      ‘merge_fields’ => [
      ‘FNAME’ => $data[‘first_name’] ?? ”,
      ‘LNAME’ => $data[‘last_name’] ?? ”,
      ‘LANDINGID’ => $post_id
      ],
      ‘tags’ => [‘shahilandin’, getthetitle($post_id)]
      ];

      wpremotepost($url, [
      ‘headers’ => [
      ‘Authorization’ => ‘Basic ‘ . base64encode(‘user:’ . $this->apikey),
      ‘Content-Type’ => ‘application/json’
      ],
      ‘body’ => jsonencode($memberdata)
      ]);
      }
      }

      new ShahiLandinMailchimpIntegration();
      `

      Custom Modules

      Creating a Module

      Modules extend ShahiLandin with new features:

      `php
      action(‘adminmenu’, [self::class, ‘register_menu’]);
      addaction(‘adminenqueuescripts’, [self::class, ‘enqueueassets’]);
      addaction(‘restapiinit’, [self::class, ‘registerrest_routes’]);
      }

      private static function is_enabled() {
      return (bool) getoption(‘shahilandincustomfeatureenabled’, false);
      }

      public static function register_menu() {
      addsubmenupage(
      ‘edit.php?posttype=shahilanding’,
      __(‘Custom Feature’, ‘shahilandin’),
      __(‘Custom Feature’, ‘shahilandin’),
      ‘manageshahilandings’,
      ‘shahilandin-custom-feature’,
      [self::class, ‘render_page’]
      );
      }

      public static function render_page() {
      ?>

      htmle(‘Custom Feature’, ‘shahilandin’); ?>

      htmle(‘Your custom feature interface here.’, ‘shahilandin’); ?>

      enqueuescript(
      ‘shahilandin-custom-feature’,
      SHAHILANDIN_URL . ‘assets/js/custom-feature.js’,
      [‘jquery’],
      SHAHILANDIN_VERSION,
      true
      );
      }

      public static function registerrestroutes() {
      registerrestroute(‘shahilandin/v1’, ‘/custom-feature’, [
      ‘methods’ => ‘GET’,
      ‘callback’ => [self::class, ‘restgetdata’],
      ‘permission_callback’ => function() {
      return currentusercan(‘manageshahilandings’);
      }
      ]);
      }

      public static function restgetdata(\WPRESTRequest $request) {
      return new \WPRESTResponse([
      ‘data’ => ‘Custom feature data’
      ]);
      }
      }
      `

      Register the module in class-plugin.php:

      `php
      private function register_hooks(): void {
      // … existing code …

      addaction(‘init’, [$this, ‘bootstrapmodules’]);
      }

      public function bootstrap_modules(): void {
      TemplateBuilderModule::bootstrap();
      TemplatesProModule::bootstrap();
      \ShahiLandin\Modules\CustomFeature\Module::bootstrap(); // Add your module
      }
      `

      Custom Templates

      Creating Custom Landing Templates

      Override default templates:

      `php
      id = getthe_ID();
      $html = getpostmeta($postid, MetaBoxes::HTMLMETAKEY, true);
      $css = getpostmeta($postid, MetaBoxes::CSSMETAKEY, true);
      ?>

      `

      Template hierarchy:

    18. {theme}/shahilandin/single-landing-{slug}.php
    19. {theme}/shahilandin/single-landing-{id}.php
    20. {theme}/shahilandin/single-landing.php
    21. {plugin}/templates/single-shahi-landing.php
    22. Custom REST Endpoints

      Extending the REST API

      Add custom endpoints to the REST API:

      `php
      CustomAPI {
      public function __construct() {
      addaction(‘restapiinit’, [$this, ‘registerroutes’]);
      }

      public function register_routes() {
      // Get landing pages by tag
      registerrestroute(‘shahilandin/v1’, ‘/landings/by-tag/(?P[a-zA-Z0-9-]+)’, [
      ‘methods’ => ‘GET’,
      ‘callback’ => [$this, ‘getlandingsby_tag’],
      ‘permission_callback’ => function() {
      return currentusercan(‘editshahilandings’);
      }
      ]);

      // Bulk update landing pages
      registerrestroute(‘shahilandin/v1’, ‘/landings/bulk-update’, [
      ‘methods’ => ‘POST’,
      ‘callback’ => [$this, ‘bulkupdatelandings’],
      ‘permission_callback’ => function() {
      return currentusercan(‘editshahilandings’);
      },
      ‘args’ => [
      ‘ids’ => [
      ‘required’ => true,
      ‘type’ => ‘array’,
      ‘items’ => [‘type’ => ‘integer’]
      ],
      ‘status’ => [
      ‘type’ => ‘string’,
      ‘enum’ => [‘publish’, ‘draft’, ‘pending’]
      ]
      ]
      ]);

      // Export landing page
      registerrestroute(‘shahilandin/v1’, ‘/landings/(?P\d+)/export’, [
      ‘methods’ => ‘GET’,
      ‘callback’ => [$this, ‘export_landing’],
      ‘permission_callback’ => function() {
      return currentusercan(‘editshahilandings’);
      }
      ]);
      }

      public function getlandingsby_tag($request) {
      $tag = $request->get_param(‘tag’);

      $args = [
      ‘posttype’ => ‘shahilanding’,
      ‘tax_query’ => [
      [
      ‘taxonomy’ => ‘shahilandingtag’,
      ‘field’ => ‘slug’,
      ‘terms’ => $tag
      ]
      ]
      ];

      $posts = get_posts($args);
      $data = [];

      foreach ($posts as $post) {
      $data[] = [
      ‘id’ => $post->ID,
      ‘title’ => $post->post_title,
      ‘link’ => get_permalink($post->ID)
      ];
      }

      return new \WPRESTResponse($data);
      }

      public function bulkupdatelandings($request) {
      $ids = $request->get_param(‘ids’);
      $status = $request->get_param(‘status’);

      $updated = [];

      foreach ($ids as $id) {
      if (currentusercan(‘editshahilanding’, $id)) {
      wpupdatepost([
      ‘ID’ => $id,
      ‘post_status’ => $status
      ]);

      $updated[] = $id;
      }
      }

      return new \WPRESTResponse([
      ‘updated’ => $updated,
      ‘count’ => count($updated)
      ]);
      }

      public function export_landing($request) {
      $postid = (int) $request->getparam(‘id’);
      $post = getpost($postid);

      if (! $post || $post->posttype !== ‘shahilanding’) {
      return new \WPError(‘notfound’, ‘Landing page not found’, [‘status’ => 404]);
      }

      $html = getpostmeta($postid, ‘shahilandin_html’, true);
      $css = getpostmeta($postid, ‘shahilandin_css’, true);

      $export = [
      ‘title’ => $post->post_title,
      ‘slug’ => $post->post_name,
      ‘html’ => $html,
      ‘css’ => $css,
      ‘meta’ => getpostmeta($post_id),
      ‘exportedat’ => currenttime(‘Y-m-d H:i:s’)
      ];

      return new \WPRESTResponse($export);
      }
      }

      new ShahiLandinCustomAPI();
      `

      Custom WP-CLI Commands

      Extending WP-CLI

      Add custom WP-CLI commands:

      `php
      CLI’) || ! WPCLI) {
      return;
      }

      class ShahiLandinCustomCLI {
      /**
      * Optimize all landing pages.
      *
      * ## EXAMPLES
      *
      * wp shahilandin-custom optimize
      */
      public function optimize($args, $assoc_args) {
      $landings = get_posts([
      ‘posttype’ => ‘shahilanding’,
      ‘post_status’ => ‘publish’,
      ‘numberposts’ => -1
      ]);

      $count = 0;

      foreach ($landings as $post) {
      $html = getpostmeta($post->ID, ‘shahilandinhtml’, true);
      $css = getpostmeta($post->ID, ‘shahilandincss’, true);

      // Minify HTML
      $html = preg_replace(‘/\s+/’, ‘ ‘, $html);
      updatepostmeta($post->ID, ‘shahilandinhtml’, $html);

      // Minify CSS
      $css = preg_replace(‘/\s+/’, ‘ ‘, $css);
      $css = preg_replace(‘/\s([{}|:;,])\s/’, ‘$1’, $css);
      updatepostmeta($post->ID, ‘shahilandincss’, $css);

      $count++;
      \WP_CLI::log(“Optimized landing page {$post->ID}”);
      }

      \WP_CLI::success(“Optimized {$count} landing pages.”);
      }

      /**
      * Generate performance report.
      *
      * ## EXAMPLES
      *
      * wp shahilandin-custom performance-report
      */
      public function performancereport($args, $assocargs) {
      $landings = get_posts([
      ‘posttype’ => ‘shahilanding’,
      ‘post_status’ => ‘publish’,
      ‘numberposts’ => -1
      ]);

      $report = [];

      foreach ($landings as $post) {
      $html = getpostmeta($post->ID, ‘shahilandinhtml’, true);
      $css = getpostmeta($post->ID, ‘shahilandincss’, true);

      $report[] = [
      ‘ID’ => $post->ID,
      ‘Title’ => $post->post_title,
      ‘HTML Size (KB)’ => round(strlen($html) / 1024, 2),
      ‘CSS Size (KB)’ => round(strlen($css) / 1024, 2),
      ‘Total Size (KB)’ => round((strlen($html) + strlen($css)) / 1024, 2)
      ];
      }

      \WPCLI\Utils\formatitems(‘table’, $report, array_keys($report[0]));
      }
      }

      \WPCLI::addcommand(‘shahilandin-custom’, ‘ShahiLandinCustomCLI’);
      `

      Testing

      PHPUnit Tests

      Create unit tests for your extensions:

      `php
      CustomTest extends WP_UnitTestCase {
      private $post_id;

      public function setUp(): void {
      parent::setUp();

      // Create a test landing page
      $this->postid = wpinsert_post([
      ‘posttype’ => ‘shahilanding’,
      ‘post_title’ => ‘Test Landing Page’,
      ‘post_status’ => ‘publish’
      ]);

      updatepostmeta($this->postid, ‘shahilandin_html’, ‘

      Test

      ‘);
      updatepostmeta($this->postid, ‘shahilandin_css’, ‘.test { color: red; }’);
      }

      public function tearDown(): void {
      wpdeletepost($this->post_id, true);
      parent::tearDown();
      }

      public function testlandingpage_created() {
      $post = getpost($this->postid);

      $this->assertEquals(‘shahilanding’, $post->posttype);
      $this->assertEquals(‘Test Landing Page’, $post->post_title);
      }

      public function testlandingpage_meta() {
      $html = getpostmeta($this->postid, ‘shahilandin_html’, true);
      $css = getpostmeta($this->postid, ‘shahilandin_css’, true);

      $this->assertEquals(‘

      Test

      ‘, $html);
      $this->assertEquals(‘.test { color: red; }’, $css);
      }

      public function testlandingservice_stats() {
      $stats = \ShahiLandin\Services\LandingService::getstatistics($this->post_id);

      $this->assertIsArray($stats);
      $this->assertArrayHasKey(‘views’, $stats);
      $this->assertArrayHasKey(‘conversions’, $stats);
      }
      }
      `

      Run tests:
      `bash
      phpunit tests/test-custom.php
      `

      Performance Best Practices

      Optimize Database Queries

      `php
      // ❌ Bad: N+1 query problem
      $landings = getposts([‘posttype’ => ‘shahi_landing’]);
      foreach ($landings as $landing) {
      $views = getpostmeta($landing->ID, ‘shahilandinviews’, true);
      }

      // ✅ Good: Single query with meta
      $landings = get_posts([
      ‘posttype’ => ‘shahilanding’,
      ‘meta_query’ => [
      [
      ‘key’ => ‘shahilandinviews’,
      ‘compare’ => ‘EXISTS’
      ]
      ]
      ]);
      `

      Use Transients for Caching

      `php
      function getlandingstatscached($postid) {
      $cachekey = “landingstats{$postid}”;
      $stats = gettransient($cachekey);

      if (false === $stats) {
      $stats = \ShahiLandin\Services\LandingService::getstatistics($post_id);
      settransient($cachekey, $stats, HOURINSECONDS);
      }

      return $stats;
      }

      // Clear cache when data changes
      addaction(‘shahilandinlandingupdated’, function($postid) {
      deletetransient(“landingstats{$postid}”);
      });
      `

      Lazy Load Heavy Operations

      `php
      // ❌ Bad: Load everything upfront
      addaction(‘admininit’, function() {
      $alllandings = getposts([‘posttype’ => ‘shahilanding’, ‘numberposts’ => -1]);
      // Process all landings…
      });

      // ✅ Good: Load on-demand
      addaction(‘adminmenu’, function() {
      addsubmenupage(
      ‘edit.php?posttype=shahilanding’,
      ‘Reports’,
      ‘Reports’,
      ‘manageshahilandings’,
      ‘shahilandin-reports’,
      function() {
      // Only load when page is accessed
      $landings = getposts([‘posttype’ => ‘shahi_landing’]);
      // …
      }
      );
      });
      `

      Security Best Practices

      Sanitize and Validate Input

      `php
      // Sanitize text input
      $title = sanitizetextfield($_POST[‘title’]);

      // Sanitize HTML
      $html = wpksespost($_POST[‘html’]);

      // Validate integer
      $postid = absint($POST[‘post_id’]);

      // Validate URL
      $url = escurlraw($_POST[‘url’]);

      // Validate email
      $email = sanitizeemail($POST[’email’]);
      `

      Check Capabilities

      `php
      // Check if user can create landing pages
      if (! currentusercan(‘createshahilandings’)) {
      wp_die(‘Insufficient permissions’);
      }

      // Check if user can edit specific landing page
      if (! currentusercan(‘editshahilanding’, $post_id)) {
      wp_die(‘You cannot edit this landing page’);
      }
      `

      Use Nonces

      `php
      // Generate nonce
      wpnoncefield(‘shahilandinsavemeta’, ‘shahilandinmetanonce’);

      // Verify nonce
      if (! isset($POST[‘shahilandinmeta_nonce’]) ||
      ! wpverifynonce($POST[‘shahilandinmetanonce’], ‘shahilandinsave_meta’)) {
      return;
      }
      `

      Troubleshooting

      Enable Debug Mode

      `php
      // In wp-config.php
      define(‘SHAHILANDIN_DEBUG’, true);

      // In your code
      if (defined(‘SHAHILANDINDEBUG’) && SHAHILANDINDEBUG) {
      errorlog(‘Debug info: ‘ . printr($data, true));
      }
      `

      Common Issues

      Problem: Changes not appearing
      Solution: Clear all caches (plugin, theme, server, CDN)

      Problem: REST API returns 401
      Solution: Check authentication, regenerate application passwords

      Problem: WP-CLI command not found
      Solution: Verify plugin is active, check namespace and class name

      Problem: Hook not firing
      Solution: Verify action/filter name, check priority and parameter count

      Additional Resources

    23. WordPress Coding Standards: https://developer.wordpress.org/coding-standards/
    24. WordPress Plugin Handbook: https://developer.wordpress.org/plugins/
    25. REST API Handbook: https://developer.wordpress.org/rest-api/
    26. WP-CLI Documentation: https://developer.wp-cli.org/
    27. PHPUnit Documentation: https://phpunit.de/documentation.html

For hooks and filters reference, see the Hooks & Filters Reference article.

Share this article

Was this article helpful?

Help us improve our documentation

Still need help?

Our support team is ready to assist you with personalized guidance for your workspace.

Submit a support ticket