Wednesday, August 27, 2025

CSV to Search Engine: How to Build a Powerful Tariff Finder with PHP and SQLite

Navigating international trade often comes down to one notoriously complex document: the Harmonized Tariff Schedule (HTS). For developers and e-commerce merchants, deciphering this data—especially from a raw CSV file—to calculate duties is a significant technical hurdle. When new tariffs are imposed, the need for a quick, accurate, and user-friendly lookup tool becomes critical. A CSV can be found Here.

A naive approach might involve a simple script that scans the CSV file for a keyword on every search. This method is not only slow but fundamentally flawed. The official HTS data is hierarchical; a product's full description is spread across multiple rows, identified only by an "Indent" level. Searching for "Nile perch," for example, gives you a row that lacks the crucial context of its parent categories, like "Other fish."

In this guide, we'll walk through a professional, scalable solution. We will build a fast, modern web application that transforms the official HTS CSV into a powerful search engine using PHP, SQLite, and vanilla JavaScript.

The Architecture: A Scalable Three-Tier Approach

To build a system that is both fast and accurate, we will separate our concerns into three distinct components:

The Importer (import.php): A one-time processing script that reads the complex, indented CSV, intelligently builds full product descriptions, and stores the clean data in a structured SQLite database.

The API (api.php): A lightweight backend endpoint that queries the pre-processed database and returns results as JSON. This decouples our data logic from the user interface.

The Frontend (index.html): A clean, responsive user interface that communicates with the API to provide a seamless search experience for the end-user.

This database-driven model ensures that searches are performed on optimized, structured data, making the application lightning-fast, regardless of the CSV's size.

Step 1: The Brains – Processing the CSV into a SQLite Database

This is the most crucial step. We create a PHP script, import.php, that acts as our one-time data processor. Its job is to read the official HTS CSV, understand the hierarchy defined by the Indent column, and construct a complete, human-readable description for every single tariff item.

The script maintains a "stack" of descriptions. As it iterates through the rows, it uses the indent level to add or replace descriptions in the stack, then joins them to form a complete trail (e.g., "Other fish - Other - Nile perch"). This full description is then inserted into a clean SQLite table alongside its HTS number and duty rates.

Here is the complete code for the importer: [import.php]

<?php

ini_set('display_errors', 1);

error_reporting(E_ALL);

set_time_limit(300); // Allow script to run for 5 minutes

// Configuration: Point to your source CSV and the desired database file

$csvFile = 'hts_2025_revision_19_csv-Copy.csv';

$dbFile = 'tariffs.db';

if (!file_exists($csvFile)) {

    die("Error: CSV file not found. Ensure it's in the same directory.");

}

try {

    // Connect to SQLite, creating the database file if it doesn't exist

    $pdo = new PDO('sqlite:' . $dbFile);

    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);


    // Re-create the table each time to ensure fresh data

    $pdo->exec("DROP TABLE IF EXISTS tariffs");

    $pdo->exec("CREATE TABLE tariffs (

        id INTEGER PRIMARY KEY AUTOINCREMENT,

        hts_number TEXT,

        full_description TEXT,

        general_rate TEXT,

        special_rate TEXT,

        column_2_rate TEXT

    )");

    echo "Database and table created successfully.<br>";

    $handle = fopen($csvFile, 'r');

    fgetcsv($handle); // Skip header row


    $pdo->beginTransaction();

    $description_stack = [];

    $rowCount = 0;

    // The core processing logic

    while (($row = fgetcsv($handle)) !== FALSE) {

        list($htsNumber, $indent, $description, $unit, $generalRate, $specialRate, $col2Rate) = array_pad($row, 7, '');

        $indent = (int)$indent;

        // Build the hierarchical description

        $description_stack[$indent] = rtrim(trim($description), ':');

        $full_description_parts = array_slice($description_stack, 0, $indent + 1);

        $fullDescription = implode(' - ', array_filter($full_description_parts));


        // Insert only the final, categorized items (those with an HTS number)

        if (!empty(trim($htsNumber))) {

            $stmt = $pdo->prepare(

                "INSERT INTO tariffs (hts_number, full_description, general_rate, special_rate, column_2_rate) 

                 VALUES (?, ?, ?, ?, ?)"

            );

            $stmt->execute([trim($htsNumber), $fullDescription, trim($generalRate), trim($specialRate), trim($col2Rate)]);

            $rowCount++;

        }

    }

    $pdo->commit();

    fclose($handle);

    echo "<h1>Success!</h1><p>Imported <strong>$rowCount records</strong> into 'tariffs.db'.</p>";

} catch (Exception $e) {

    if ($pdo->inTransaction()) {

        $pdo->rollBack();

    }

    die("An error occurred: " . $e->getMessage());

}

?>

Step 2: The Engine – Building a Fast Search API

With our data neatly organized, we need an efficient way to query it. The api.php script serves this purpose. It accepts a search term, runs a simple but powerful LIKE query against the full_description column in our SQLite database, and returns the findings as a JSON object. This approach is highly scalable, as database queries are significantly faster than file-system operations.

api.php

<?php

header('Content-Type: application/json');

$dbFile = 'tariffs.db';

$response = ['error' => 'An unknown error occurred.'];

if (!file_exists($dbFile)) {

    echo json_encode(['error' => 'Database not found. Please run import.php first.']);

    exit;

}

$searchTerm = isset($_GET['query']) ? trim($_GET['query']) : '';

if (strlen($searchTerm) < 3) {

    echo json_encode(['error' => 'Search term must be at least 3 characters.']);

    exit;

}

try {

    $pdo = new PDO('sqlite:' . $dbFile);

    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Prepare and execute the search query

    $stmt = $pdo->prepare(

        "SELECT hts_number, full_description, general_rate, special_rate, column_2_rate 

         FROM tariffs 

         WHERE full_description LIKE ?

         LIMIT 50" // Limit results for performance

    );

    $stmt->execute(['%' . $searchTerm . '%']);

    $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

    echo json_encode($results);

} catch (Exception $e) {

    echo json_encode(['error' => 'Database query failed: ' . $e->getMessage()]);

}

?>

Step 3: The Interface – A User-Friendly Frontend

The final piece is the index.html file, which provides the user experience. It contains a search box that uses modern JavaScript (fetch API) to communicate with our api.php backend asynchronously. To enhance usability, a "debounce" mechanism is implemented to prevent sending a request on every keystroke, instead waiting until the user has paused typing.

The interface intelligently handles the results: if only one match is found, it displays the full details directly. If multiple matches are found, it presents them as a list of choices, allowing the user to select the correct one.

index.html

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Professional Tariff Finder</title>

    <style>

        :root {

            --primary-color: #005A9C; --secondary-color: #f0f6fa; --border-color: #d0d7de;

            --text-color: #24292f; --highlight-bg: #fffbdd;

        }

        body { font-family: sans-serif; line-height: 1.6; color: var(--text-color); max-width: 900px; margin: 20px auto; padding: 20px; }

        h1 { color: var(--primary-color); border-bottom: 2px solid var(--primary-color); padding-bottom: 10px; }

        #searchBox { width: 100%; padding: 15px; font-size: 18px; border: 1px solid var(--border-color); border-radius: 6px; box-sizing: border-box; }

        #status { margin-top: 15px; color: #666; min-height: 20px; }

        .result-item { border: 1px solid var(--border-color); border-radius: 6px; padding: 15px; margin-bottom: 10px; }

        .result-item.choice:hover { background-color: var(--secondary-color); cursor: pointer; }

        .result-item h3 { margin: 0 0 10px 0; color: var(--primary-color); }

        .result-item .rate { font-weight: bold; color: #c9372c; }

        #back-button { display: none; margin-bottom: 15px; padding: 8px 15px; border: 1px solid var(--border-color); border-radius: 6px; cursor: pointer; }

    </style>

</head>

<body>

    <h1>U.S. Harmonized Tariff Schedule Search</h1>

    <p>Enter a product description (e.g., "ceramic cup", "nile perch") to find probable tariff categories.</p>

    <input type="text" id="searchBox" placeholder="Start typing a product name..." autocomplete="off">

    <div id="status"></div>

    <button id="back-button">&laquo; Back to Search Results</button>

    <div id="results"></div>


<script>

    const searchBox = document.getElementById('searchBox');

    const resultsContainer = document.getElementById('results');

    const statusContainer = document.getElementById('status');

    const backButton = document.getElementById('back-button');

    let debounceTimer, lastResults = [];

    searchBox.addEventListener('input', () => {

        clearTimeout(debounceTimer);

        debounceTimer = setTimeout(performSearch, 300);

    });

   backButton.addEventListener('click', () => displayResults(lastResults, false));

    function performSearch() {

        const query = searchBox.value.trim();

        if (query.length < 3) {

            resultsContainer.innerHTML = '';

            statusContainer.textContent = 'Please enter at least 3 characters.';

            return;

        }

        statusContainer.textContent = 'Searching...';

        fetch(`api.php?query=${encodeURIComponent(query)}`)

            .then(res => res.json()).then(data => {

                if (data.error) throw new Error(data.error);

                lastResults = data;

                displayResults(data);

            }).catch(err => {

                statusContainer.textContent = '';

                resultsContainer.innerHTML = `<div class="result-item"><strong>Error:</strong> ${err.message}</div>`;

            });

    }


    function displayResults(data, isNewSearch = true) {

        resultsContainer.innerHTML = '';

        backButton.style.display = 'none';

        if (isNewSearch) statusContainer.textContent = `${data.length} potential match(es) found.`;

        if (data.length === 1) return displaySingleResult(data[0]);

        data.forEach(item => {

            const div = document.createElement('div');

            div.className = 'result-item choice';

            div.innerHTML = `<h3>${item.hts_number}</h3><p>${item.full_description}</p><p><strong>General Rate:</strong> <span class="rate">${item.general_rate || 'N/A'}</span></p>`;

            div.addEventListener('click', () => displaySingleResult(item));

            resultsContainer.appendChild(div);

        });

    }

    function displaySingleResult(item) {

        statusContainer.textContent = 'Showing details for your selection.';

        if (lastResults.length > 1) backButton.style.display = 'block';

        resultsContainer.innerHTML = `

            <div class="result-item">

                <h3>${item.hts_number}</h3>

                <p><strong>Full Description:</strong> ${item.full_description}</p>

                <p><strong>General Rate of Duty:</strong> <span class="rate">${item.general_rate || 'N/A'}</span></p>

                <p><strong>Special Rate of Duty:</strong> <span>${item.special_rate || 'N/A'}</span></p>

                <p><strong>Column 2 Rate of Duty:</strong> <span>${item.column_2_rate || 'N/A'}</span></p>

            </div>`;

    }

</script>

</body>

</html>


Launching The Tariff Finder

Setup: Place the three files (import.php, api.php, index.html) and your source hts .. csv file in a directory on a web server with PHP and the SQLite extension enabled.

Import Data: Run the importer script once by navigating to your-domain.com/import.php in your browser. You should see a success message. This will create the tariffs.db file.

Launch: Open index.html in your browser. You can now start searching.


Conclusion and Next Steps :: 

By investing a little time in pre-processing the data, we have transformed an unusable CSV into a high-performance search application. This professional approach not only provides a superior user experience but also creates a robust foundation that can be easily extended.

From here, you could enhance the system further by:

Implementing SQLite's Full-Text Search (FTS5) for more advanced, weighted searching.

Adding a frontend framework like Vue or React for a more complex UI.

Creating a cron job to run the import.php script periodically to keep the tariff data up-to-date automatically.

This solution empowers developers to tackle complex data challenges with elegant, efficient, and scalable code, turning a business problem into a powerful internal tool. 

Tuesday, August 26, 2025

🛡 PHP Session Hijacking Detection & Prevention

 Building a System to Monitor Session Anomalies and Auto-Invalidate Suspicious Sessions


🔍 Introduction

Session hijacking is a silent predator in the world of web security. In PHP applications, sessions are the lifeline of user authentication — but if an attacker steals a valid session ID, they can impersonate the user without ever knowing their password.

While HTTPS and secure cookies help, they don't guarantee immunity. That's why adding session anomaly detection — such as monitoring IP address changes and User‑Agent mismatches — is a smart, proactive defense.


⚠ Understanding Session Hijacking

Session hijacking happens when an attacker gains access to a valid session token (usually stored in a cookie). Common attack vectors include:

  • Packet sniffing on unsecured networks.
  • Cross‑site scripting (XSS) stealing cookies.
  • Malware on the client device.
  • Session fixation attacks.

Once the attacker has the token, they can bypass login entirely.


🧠 The Detection Strategy

We'll use two key indicators to detect anomalies:


A. IP Address Tracking

  • Store the user's IP at login.
  • Compare it on every request.
  • If it changes unexpectedly, flag it.


B. User‑Agent Verification

  • Store the browser's User‑Agent string at login.
  • Compare it on every request.
  • If it changes, it could mean a hijacked session from a different device/browser.


🛠 Implementation in PHP

<?php

session_start();


// Get client IP

function getClientIP() {

    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {

        return $_SERVER['HTTP_CLIENT_IP'];

    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {

        return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];

    } else {

        return $_SERVER['REMOTE_ADDR'];

    }

}


// Initialize security data on login

function initSessionSecurity() {

    $_SESSION['IP_ADDRESS'] = getClientIP();

    $_SESSION['USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];

    $_SESSION['LAST_ACTIVITY'] = time();

}


// Validate session on each request

function validateSession() {

    if ($_SESSION['IP_ADDRESS'] !== getClientIP()) {

        destroySession("IP address mismatch");

    }

    if ($_SESSION['USER_AGENT'] !== $_SERVER['HTTP_USER_AGENT']) {

        destroySession("User-Agent mismatch");

    }

    $timeout = 900; // 15 minutes

    if (time() - $_SESSION['LAST_ACTIVITY'] > $timeout) {

        destroySession("Session timed out");

    }

    $_SESSION['LAST_ACTIVITY'] = time();

}


// Destroy session and redirect

function destroySession($reason) {

    session_unset();

    session_destroy();

    header("Location: login.php?error=" . urlencode($reason));

    exit();

}

?>

Usage:

  • Call initSessionSecurity() after successful login.
  • Call validateSession() at the start of every protected page.


📊 Conceptual Diagram

+-------------------+

| User Logs In      |

+-------------------+

         |

         v

+-------------------+

| Store IP & UA     |

+-------------------+

         |

         v

+-------------------+

| Each Request      |

| Compare IP & UA   |

+-------------------+

   |           |

   | Match     | Mismatch

   v           v

Continue   Destroy Session


🔒 Extra Hardening Tips

  • Regenerate Session IDs periodically (session_regenerate_id(true)).
  • Use Secure Cookies with HttpOnly and Secure flags.
  • Force HTTPS site‑wide.
  • Limit Session Lifetime to reduce exposure.
  • Store Sessions in a Database for centralized monitoring.


🚀 Conclusion

By adding IP and User‑Agent anomaly detection in PHP, you create a second line of defense that forces attackers to perfectly mimic the user's environment — a much harder task than just stealing a cookie.

Security is an ongoing process. Keep refining your detection logic, monitor logs, and combine this with other best practices for a truly resilient PHP application.

Monday, July 10, 2017

Adding a Simple product to Cart in Magento externally

In this article, we'll see how we can insert a simple product to Magento Cart from an external PHP script.

We are testing this on Magento 1.9.2.2 and we have a simple product with ID 375 which we'll push to Cart. 

Let's see the code below. First, we are showing the existing Cart, then we'll insert a simple product ID 375, and finally we'll print the new Cart.

<?php
// Load Magento 
require_once("app/Mage.php");
umask(0);
Mage::app();

/// We Build a String for printing
$str = "";
$str .= "1. Showing existing Cart<br>===============<br>";

/// This line connects to the FrontEnd session
Mage::getSingleton('core/session', array('name' => 'frontend'));  

// Get customer session
$session = Mage::getSingleton('customer/session'); 

// Get cart instance
$cart = Mage::getSingleton('checkout/cart'); 
$cart->init();

// Get CART Count
$cartItemsCount = $cart->getItemsCount();
$str .= "Count >> $cartItemsCount <br>";

// GET CART Items
$quote = $cart->getQuote();
$cartItems = $quote->getAllVisibleItems();

// Loop Through ITEMs
foreach ($cartItems as $item) 
{
    $productId = $item->getProductId();
    $product = Mage::getModel('catalog/product')->load($productId);
$str .= "Id: $productId, Price: " . $item->getPrice() 
                . ", Qty:" . $item->getQty() . "<br>";
    // Do something more
}

// Build String
$str .= "<br><br>2. Adding a New Product<br>===============<br>";

// SET Product ID
$productId = 375;

// Build Product Params
$productInstance = Mage::getModel('catalog/product')->load($productId);
$param = array(
'product' => $productInstance->getId(),
'qty' => 1
);

// Finally ADD the product to CART
$cart->addProduct($productInstance, $param);            
// Save The Cart
$cart->save(); 

// Update Session
$session->setCartWasUpdated(true);

// Building the String
$str .= "<br><br>3. Showing the new Cart<br>===============<br>";

// Get cart instance
$cart = Mage::getSingleton('checkout/cart'); 
$cart->init();

// Get CART Count
$cartItemsCount = $cart->getItemsCount();
$str .= "Count >> $cartItemsCount <br>";

// GET CART Items
$quote = $cart->getQuote();
$cartItems = $quote->getAllVisibleItems();

// Loop Through ITEMs
foreach ($cartItems as $item) 
{
    $productId = $item->getProductId();
    $product = Mage::getModel('catalog/product')->load($productId);
    $str .= "Id: $productId, Price: " . $item->getPrice() 
            . ", Qty:" . $item->getQty() . "<br>";
    // Do something more
}

// Print the String
echo $str;
?>

The above code is quite self explanatory. We tried to connect to frontend customer's session and get his Cart details. If customer is not logged in, then it does not create any problem.

See some screenshot here : 

Step 1. Existing Cart


Step 2. Our Script Runs



Step 3. Final Cart




In this example, we added a simple product. To add a configurable product to Cart, we need to do some extra work which I'll show in next article.

Hope this helps.

Saturday, June 24, 2017

Running Laravel on Windows

Here I'll discuss about how we can install and run Laravel on Windows environment and start development in Laravel. 

First step is to download the Laravel package from https://github.com/laravel/laravel and Unzip it in a folder, say C:\xampp2\htdocs\laravel. Notice that my Xampp is installed in c:\xampp2.

Now we have two ways we can start the Laravel project running.

First Method :: From Command Line

A. First, create a DB through phpmyadmin, the name I gave is 'homestead'

B. Secondly, rename the file .env.example to .env file which holds all the basic configuration for running Laravel. It contains the DB configuration, application URL. We need to make changes accordingly. 
  
See that APP_KEY= is still left blank. We'll generate a key for our application shortly.

C. In this situation, if we try yo run Laravel server by issuing command "php artisan serve", it will show some "file not found" error as some dependencies are still unavailable.  



  
  We need to install them by issuing "composer install". 
  

  
D. After the command "composer install" ran, we can see that a new folder called "vendor" is created where various libraries are installed. 

E. Now, see what happens if we try to run the server by issuing "php artisan serve

    The server is run. Below message is shown in the command line .. 
   
    Laravel Development Server started: <http://127.0.0.1:8000>
   
   and if we hit the URL http://127.0.0.1:8000 in browser, see what happens
   
  


   
So, we need to follow some more steps as shown below.

F. Press ctrl + c to stop the server. Run command "php artisan key:generate" to create new Security Key.

   If the command line console shows any message like "Dotenv values containing spaces must be surrounded by quotes.", it means any settings provided in .env file need to be wrapped by a quote (") or (').
   
   

   
   See, it is reported that an Application key has been generated. The environment configuration file .env is updated also with the new key. In my case, it is as shown below :
   
 APP_KEY=base64:6Ed0zZN/3u+Q58OyZn6JnTvXjO7/vxM0Jdvder18QU8=
   
G. Now issue "php artisan serve" to start your server. Visit the webpage at http://127.0.0.1:8000.

Second Method :: The other way

A. The steps till Security Key Generation shown above are still required here. But we don't need to issue the command 'php artisan serve' here ...

Start your Xampp. In my case, I have a configuration where port 80 is listened by IIS, 8082 is listened by Xampp Apache. 

I have Xampp 3.2.2 installed on my Windows 8. In my case, Xampp is installed in c:\xampp2 folder.

Here, I'll be assigned the Laravel to a different port 8060.  

So, I just create a Virtual Host in file c:\xampp2\apache\conf\extra\httpd-vhosts.conf file as shown below.

<VirtualHost *:8060>
    DocumentRoot "C:\xampp2\htdocs\laravel\public"
    ServerName laravel.dev
</VirtualHost>

So, my Xampp will listen/serve to 8060 port as well.

If I change this 8060 to default 8082, my Xampp Home page (http://127.0.0.1:8082) will be replaced by Laravel, which I don't want.

B. Next, start the Xampp server. See below, the port numbers apache is serving now.




C. Hit "http://127.0.0.1:8060/" in the browser URL to see Laravel running.

D. You can also modify c:\windows\system32\drivers\etc\hosts file to have entry like this :
   
  127.0.0.1:8060  laravel.dev
   
   So, if you enter 'laravel.dev' in address bar of the browser, it will point to 127.0.0.1:8060.

The 'public' directory inside the "Laravel" installation contains the index.php file, which is the entry point for all requests to our  application. This directory also holds all JavaScript, images and CSS.

I hope this helps.

Friday, August 12, 2016

How to Show All Magento Products Externally in PHP

In our previous article, we have seen how to display all the Magento orders externally (Not within Magento itself) in php.

Now, let's see a piece of code which applies same logic and show all products. Let's check out the code.

<?php
require_once 'app/Mage.php';
umask(0);
Mage::app();
// SEARCH MODE
if($_GET['q'] && trim($_GET['q']) !="")
{
    $collection =                                                                Mage::getResourceModel('catalog/product_collection')
             ->addAttributeToSelect('*')
            ->addAttributeToFilter( 
              array(
array('attribute'=> 'name',
      'like' => '%'.trim($_GET['q']).'%'
                     ),
array('attribute'=> 'sku',
                   'like' => '%'.trim($_GET['q']).'%'
                     ),
    ))
             ->load();
}
else
/// Normal Procedure
{
   $collection = Mage::getModel('catalog/product')
                ->getCollection()
                ->addAttributeToSelect('*')
->addAttributeToSort('name', 'ASC')
->load();
}
?>

The above code includes the Search handling part also. The condition if($_GET['q'] && trim($_GET['q']) !="") evaluates to true when user enters some keyword and hits Search button.

->addAttributeToFilter( array(
array('attribute'=> 'name',
      'like' => '%'.trim($_GET['q']).'%'
                      ),
array('attribute'=> 'sku',
      'like' => '%'.trim($_GET['q']).'%'
                      ),
))

Here, the addAttributeToFilter() generates an SQL like this

WHERE 'name' LIKE '%search%' OR 'sku' LIKE '%search%'

Next, we just need to iterate through the product collection $collection.

<table width="100%" border="0">
  <tr>
  <td colspan="5" align="center">
   <div>
    <form method="get">
      <input type="text" value="<?php echo $_GET['q'];?>" name="q">
      <button title="Search" type="submit">Search</button>
    </form>
   </div>
  </td>
  </tr>
  <tr class="header">
    <th width=""><strong>ID</strong></th>
    <th width=""><strong>Name</strong></th>
    <th width=""><strong>SKU</strong></th>
    <th width=""><strong>Price</strong></th>
    <th width=""><strong>Final Price</strong></th>
  </tr>
 <?php 
  // LOOP the Collection
  foreach ($collection as $_product)
  {
    $productID = $_product->getId();
    $_product  = Mage::getModel('catalog/product')
                  ->load($productID);
    $productPosition = 0;

    // Product is Enabled and VISIBILITY = Search, Catalog 
    if($_product->getStatus()==1 && $_product->getVisibility()==4)
    {
       // GET FINAL PRICE aftyer applying RULE
       $finalPrice = Mage::getModel('catalogrule/rule')
                     ->calcProductPriceRule( $_product, 
                      $_product->getPrice() );
       $_specialPrice = $_product->getFinalPrice();
       if($_specialPrice<=$finalPrice)
       {
 $finalPrice = $_specialPrice;
       }
       if($finalPrice)
       {
 $finalPrice = Mage::helper('core')->currency( 
                           $finalPrice, true, false);
       }
       else
       {
  $finalPrice = Mage::helper('core')->currency(
                 $_product->getFinalPrice(), true, false);
       }

       echo "<tr>";
       echo "<td>".$productID."</td>";
       echo "<td><a href='" . 
             $_product->getProductUrl() .
            "'>" . $_product->getName() .                                            "</a></td>";
       echo "<td>".$_product->getSku()."</td>";
       echo "<td>".Mage::helper('core')->currency( 
                 $_product->getPrice(), true, false).                                    "</td>";
       echo "<td>".$finalPrice."</td>";
       echo "</tr>";

    }
 }
?>
</table>

We are showing all the products which have Visibility 4 (i.e "Catalog, Search"). Also the product's final price is the price we get after applying any Rules.

Check the screenshot below.



Hope this helps.

How to Show All Magento Orders Externally in PHP

The main objective is to show all the orders on a separate PHP page which won't be part of Magento.

So, we create a PHP script salesorders.php and put it in the root folder "public_html". We would use Magento's functionality by including its core files.

Now, let's check how we should start ...

<?php
error_reporting(1);

// Here We load Mage class
require_once 'app/Mage.php';

// BootStrap the Magento
Mage::app();

// GET ALL ORDERS
$orders = Mage::getResourceModel('sales/order_collection')
          ->addAttributeToSelect('*')
          ->addFieldToFilter('status', 
         array("in" => array('complete', 'closed'))
    )
          ->addAttributeToFilter('store_id', 
                  Mage::app()->getStore()->getId())
          ->addAttributeToSort('created_at', 'desc')
          ->load();
?>

In the above code, we are selecting all the Orders which have status either 'complete' or 'closed'. Also, we are fetching the Orders with the 'created_at' field in descending order.

Then we just need to iterate through the collection and generate an HTML. 

<?php 
foreach($orders as $order)
{
  /// GRAND TOTAL
  $grand_total = $order -> grand_total;

  /// CUSTOMER DETAILS
  $customer_email = $order -> customer_email;
  $customer_fname = $order -> customer_firstname;
  $customer_lname = $order -> customer_lastname;

  /// IF Customer names are blanks
  if($customer_fname == '' )
  {
   $billing_address_data = $order->getBillingAddress()->getData();
   $customer_fname = $billing_address_data['firstname'];
  }
  if($customer_lname == '' )
  {
    $billing_address_data = $order->getBillingAddress()->getData();
    $customer_lname = $billing_address_data['lastname'];
  }

  /// ORDER ID
  $increment_id = $order -> increment_id;
  $created_at   = $order -> created_at;

  $str  = "<tr>";
  $str .= "<td>$customer_fname $customer_lname <i>                              ($customer_email)</i></td>";
  $str .= "<td><b>$increment_id</b> Created on                              $created_at</td>";
  $str .= "<td>";

  /// GET all Visible Products
  /// purchased in the ORDER
  $items = $order->getAllVisibleItems();
  $largeItems = 0;

  /// LOOP thru ITEMs
  foreach($items as $i)
  {
    /// PRODUCT DETAILS
    $prod_id = $i->getProductId();
    $p = Mage::getModel('catalog/product')->load($prod_id);
    $sku = $p->getSku();
  
    /// Build HTML
    $str .=  "<a href='" . $p->getUrlPath() . "'>" . $i->getName() . "</a>";
  
    /// PRODUCT Options
    $prodOptions = $i->getProductOptions();
    /// LOOP thru product Options and Show Them
    foreach ( $prodOptions['attributes_info'] as $key => $val)
    {
$str .= "[" . $val['label'] . ":"; 
       $str .= $val['value'] . "] ";
    } 
  }

  $str .= "</td>";
  $str .= "</tr>";

  /// PRINT HTML
  echo $str ;
}
?>

Now check the Output we get.




Now, we can add a Search facility to our script; it will search the POSTed word with Customer name or email within the order. So, we need a <form> tag in our HTML. 

<form method="get" action="" id="search_mini_form">
    <input type="text" name="q" placeholder="Search Customer">
    <input  title="Search" type="submit" value="Search">
</form>

So, through PHP we need to receive this POSTed value and add some filtering code to the Order collection. The code is shown below. 

<?php
/// SEARCH 
if($_GET['q'] && trim($_GET['q']) != "")
{
  /// LIKE Query
  $likeStr = '%'.trim($_GET['q']).'%';

  $orders = Mage::getResourceModel('sales/order_collection')
      ->addAttributeToSelect('*')
      ->addFieldToFilter('status', array("in" => array(
           'complete', 'closed')
        ))
      ->addAttributeToFilter( 'store_id', 
               Mage::app()->getStore()->getId())
      ->addFieldToFilter( 
             array( 'customer_email', 'customer_firstname', 'customer_lastname'),
     array( array( 'like'=>$likeStr ), 
            array( 'like'=>$likeStr ), 
    array( 'like'=>$likeStr ) 
  )
)
      ->addAttributeToSort('created_at', 'desc')
      ->load();
    
  /// IF u want to show SQL
  /// uncomment below line
  /// echo $orders->getSelect()->__toString();
}
else
/// FOR Non-Search 
{
   $orders = Mage::getResourceModel('sales/order_collection')
       ->addAttributeToSelect('*')
       ->addFieldToFilter('status', array("in" => array(
            'complete', 'closed')
          ))
       ->addAttributeToFilter('store_id', 
            Mage::app()->getStore()->getId())
       ->addAttributeToSort('created_at', 'desc')
       ->load();

?>

If it is a search, then if($_GET['q'] && trim($_GET['q']) != "") is true and the first part of the if-else structure is executed. 

See, how we have captured the submitted value in variable "$likeStr" and used "addFieldToFilter" function for filtering the collection.

IF we need "AND" conditions, then we can use multiple addFieldToFilter() calls.

To use "OR", we need to modify the addFieldToFilter() as shown below.

->addFieldToFilter( 
array( 'customer_email', 
               'customer_firstname', 
               'customer_lastname'),
array( array( 'like'=>$likeStr ), 
      array( 'like'=>$likeStr ), 
       array( 'like'=>$likeStr ) 
     )
  )

above statement generates the following SQL

'customer_email' like $likeStr OR 'customer_firstname' like $likeStr OR 'customer_lastname' like $likeStr

To generate an "AND" SQL query like this :: 
'customer_email' like $likeStr AND 'customer_firstname' like $likeStr, 

we can use the following structure ::

->addFieldToFilter('customer_email',  array("like" => $likeStr))
->addFieldToFilter('customer_firstname',array('like'=>$likeStr))

You can download the full working code here.

In our next tutorial, we'll list all the products available in Magento from an external script.