This commit is contained in:
@@ -1,54 +1,36 @@
|
||||
# OpenCart 3 Module Template
|
||||
# Prevent PO Box Address for OpenCart 3
|
||||
|
||||
A comprehensive template for developing custom modules and extensions for OpenCart 3.x e-commerce platform.
|
||||
An OpenCart 3 extension template that blocks PO Box addresses during checkout and improves shipping address validation.
|
||||
|
||||
## Features
|
||||
|
||||
### 🚀 OpenCart 3 Compatibility
|
||||
- Fully compatible with OpenCart 3.x versions
|
||||
- Uses OCMOD (OpenCart Modification) system for safe modifications
|
||||
- No core file changes required
|
||||
|
||||
### 📦 Module Structure
|
||||
- **install.xml**: OCMOD installation file with modification definitions
|
||||
- **upload/**: Directory for module files to be uploaded to OpenCart
|
||||
- **install.php**: Optional PHP installation script
|
||||
- **install.sql**: Optional database installation script
|
||||
|
||||
### 🔄 Automated Releases
|
||||
- Integrated Gitea Actions workflow for automatic builds
|
||||
- Generates `.ocmod.zip` packages on push to main branch
|
||||
- Extracts metadata from `install.xml` for versioning
|
||||
- Ready-to-install extension packages
|
||||
|
||||
### 🛠️ Development Tools
|
||||
- XML-based modification system
|
||||
- Template structure for rapid module development
|
||||
- CI/CD pipeline for quality assurance
|
||||
- Version management through install.xml
|
||||
- Prevents PO Box addresses in shipping and billing forms
|
||||
- Works with OpenCart 3.x and the OCMOD modification system
|
||||
- No core file edits required
|
||||
- Easy to install as a `.ocmod.zip` extension package
|
||||
- Includes a ready-to-use `install.xml` definition and upload package structure
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone this repository
|
||||
2. Customize the `install.xml` with your module details:
|
||||
- Set `<name>`, `<code>`, and `<version>`
|
||||
- Add your modifications in the `<file>` sections
|
||||
3. Place your module files in the `upload/` directory
|
||||
4. Add installation scripts if needed (`install.php`, `install.sql`)
|
||||
5. Push to your repository to trigger automatic release build
|
||||
1. Download or clone this repository.
|
||||
2. Open the OpenCart admin panel and go to `Extensions > Installer`.
|
||||
3. Upload the generated `.ocmod.zip` package.
|
||||
4. Go to `Extensions > Modifications` and click `Refresh`.
|
||||
5. Open `Extensions > Extensions`, choose `Shipping` or the appropriate extension type, and install the module if needed.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Customize `install.xml` if required:
|
||||
- Update `<name>`, `<code>`, and `<version>`
|
||||
- Adjust the address validation rules in the modification definitions
|
||||
2. Place any additional files in the `upload/` directory for packaging.
|
||||
3. If you add install scripts, include `install.php` or `install.sql`.
|
||||
|
||||
## Usage
|
||||
|
||||
### For Module Developers
|
||||
1. Use this template as a starting point for new OpenCart modules
|
||||
2. Define your modifications in `install.xml`
|
||||
3. Add custom files to `upload/` directory
|
||||
4. Test locally before deploying
|
||||
|
||||
### For Store Owners
|
||||
1. Download the generated `.ocmod.zip` from releases
|
||||
2. Upload via OpenCart's Extension Installer
|
||||
3. Install and configure the module through admin panel
|
||||
- After installation, checkout address fields are validated for PO Box patterns.
|
||||
- Users attempting to enter a PO Box address will be prompted to use a street address instead.
|
||||
- Test on a development store before deploying to production.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -60,9 +42,8 @@ A comprehensive template for developing custom modules and extensions for OpenCa
|
||||
## Author
|
||||
|
||||
**Mohamed Youssef**
|
||||
Website: [my-dev.pro](https://my-dev.pro)
|
||||
Email: Contact through website
|
||||
Website: [my-dev.pro](https://my-dev.pro)
|
||||
|
||||
## License
|
||||
|
||||
This template is provided as-is for OpenCart module development. Please check individual module licenses for usage terms.
|
||||
This extension template is provided as-is for OpenCart module development. Review and adapt it to your own store policies and licensing needs.
|
||||
|
||||
+233
-2
@@ -1,11 +1,242 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<modification>
|
||||
<name></name>
|
||||
<code></code>
|
||||
<name>Prevent PO Box Addresses</name>
|
||||
<code>mydev_prevent_po_box_addresses</code>
|
||||
<author><![CDATA[<b style="color: green">MY-Dev | </b><b>Mohamed Youssef</b>]]></author>
|
||||
<link>https://my-dev.pro</link>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<file path="catalog/model/account/address.php">
|
||||
<operation error="log">
|
||||
<search><![CDATA[public function getTotalAddresses() {]]></search>
|
||||
<add position="before"><![CDATA[
|
||||
private function getRequest(string $url): ?array
|
||||
{
|
||||
$connection = curl_init();
|
||||
|
||||
curl_setopt_array($connection, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
]);
|
||||
|
||||
$response = curl_exec($connection);
|
||||
|
||||
if ($response === false) {
|
||||
curl_close($connection);
|
||||
return [];
|
||||
}
|
||||
|
||||
$status = curl_getinfo($connection, CURLINFO_HTTP_CODE);
|
||||
curl_close($connection);
|
||||
|
||||
if ($status !== 200) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
// fetch longitude & latitude based on ZIP Code
|
||||
private function getCustomerLocation(string $zipcode, string $countryCode = 'US'): array
|
||||
{
|
||||
$url = sprintf(
|
||||
'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?SingleLine=%s&f=json&outFields=*&countryCode=%s',
|
||||
urlencode((string)$zipcode),
|
||||
urlencode($countryCode)
|
||||
);
|
||||
|
||||
$response = $this->getRequest($url);
|
||||
if (empty($response['candidates'])) {
|
||||
return [];
|
||||
}
|
||||
return $response['candidates'][0]['location'];
|
||||
}
|
||||
|
||||
private function getAvailableBoxes(string $zipcode, string $countryCode = 'US'): array
|
||||
{
|
||||
$location = $this->getCustomerLocation($zipcode, $countryCode);
|
||||
|
||||
if (empty($location)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$longitude = $location['x'];
|
||||
$latitude = $location['y'];
|
||||
|
||||
$url = sprintf(
|
||||
'https://poboxes.usps.com/pobol/app/poLocations/search?longitude=%s&latitude=%s',
|
||||
$location['x'],
|
||||
$location['y']
|
||||
);
|
||||
|
||||
return $this->getRequest($url);
|
||||
|
||||
}
|
||||
|
||||
private function normalizeAddress(string $address): string
|
||||
{
|
||||
$address = strtoupper(trim($address));
|
||||
|
||||
// Remove punctuation
|
||||
$address = preg_replace('/[.,#]/', ' ', $address);
|
||||
|
||||
// Normalize whitespace
|
||||
$address = preg_replace('/\s+/', ' ', $address);
|
||||
|
||||
$replacements = [
|
||||
// Directions
|
||||
'NORTHWEST' => 'NW',
|
||||
'NORTHEAST' => 'NE',
|
||||
'SOUTHWEST' => 'SW',
|
||||
'SOUTHEAST' => 'SE',
|
||||
'NORTH' => 'N',
|
||||
'SOUTH' => 'S',
|
||||
'EAST' => 'E',
|
||||
'WEST' => 'W',
|
||||
|
||||
// Street suffixes
|
||||
'STREET' => 'ST',
|
||||
'AVENUE' => 'AVE',
|
||||
'ROAD' => 'RD',
|
||||
'DRIVE' => 'DR',
|
||||
'BOULEVARD' => 'BLVD',
|
||||
'LANE' => 'LN',
|
||||
'COURT' => 'CT',
|
||||
'CIRCLE' => 'CIR',
|
||||
'PLACE' => 'PL',
|
||||
'PARKWAY' => 'PKWY',
|
||||
'TERRACE' => 'TER',
|
||||
'HIGHWAY' => 'HWY',
|
||||
'SQUARE' => 'SQ',
|
||||
'TRAIL' => 'TRL',
|
||||
'WAY' => 'WAY',
|
||||
'LOOP' => 'LOOP',
|
||||
'CENTER' => 'CTR',
|
||||
'MOUNT' => 'MT',
|
||||
'JUNCTION' => 'JCT',
|
||||
|
||||
// Common unit designators
|
||||
'APARTMENT' => 'APT',
|
||||
'SUITE' => 'STE',
|
||||
'BUILDING' => 'BLDG',
|
||||
'FLOOR' => 'FL',
|
||||
'ROOM' => 'RM',
|
||||
|
||||
// PO Box
|
||||
'POST OFFICE BOX' => 'PO BOX',
|
||||
'POST OFFICE' => 'PO',
|
||||
];
|
||||
|
||||
foreach ($replacements as $search => $replace) {
|
||||
$address = preg_replace(
|
||||
'/\b' . preg_quote($search, '/') . '\b/',
|
||||
$replace,
|
||||
$address
|
||||
);
|
||||
}
|
||||
|
||||
// Normalize whitespace again
|
||||
$address = preg_replace('/\s+/', ' ', trim($address));
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
public function hasPOBoxFacility(string $zipCode, string $address): bool
|
||||
{
|
||||
|
||||
$status = false;
|
||||
$facilities = $this->getAvailableBoxes((string)$zipCode);
|
||||
|
||||
if (empty($facilities)) {
|
||||
return $status;
|
||||
}
|
||||
|
||||
$searchAddress = $this->normalizeAddress($address);
|
||||
$address_zipcode = explode('-', $zipCode); // Use only the first part of the ZIP code
|
||||
$zipcode = $address_zipcode[0];
|
||||
$zip4code = $address_zipcode[1] ?? null;
|
||||
|
||||
foreach ($facilities as $facility) {
|
||||
if (($facility['address']['zipCode'] ?? '') !== $zipcode ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$facilityAddress = $this->normalizeAddress($facility['address']['addressLine1']);
|
||||
|
||||
if (strpos($searchAddress, $facilityAddress) !== false || strpos($facilityAddress, $searchAddress) !== false) {
|
||||
$status = true;
|
||||
}
|
||||
|
||||
if (($facility['address']['zip4Code'] ?? '') !== $zip4code) {
|
||||
$status = false;
|
||||
}
|
||||
|
||||
if ($status) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
]]></add>
|
||||
</operation>
|
||||
</file>
|
||||
|
||||
<file path="catalog/controller/checkout/{payment_address,shipping_address}.php">
|
||||
<operation error="log">
|
||||
<search><![CDATA[if ($this->request->post['country_id'] == '') {]]></search>
|
||||
<add position="before"><![CDATA[
|
||||
if ($this->model_account_address->hasPOBoxFacility($this->request->post['postcode'], $this->request->post['address_1'])) {
|
||||
$json['error']['address_1'] = $this->language->get('error_po_box');
|
||||
}
|
||||
]]></add>
|
||||
</operation>
|
||||
</file>
|
||||
|
||||
<file path="catalog/controller/account/address.php">
|
||||
<operation error="log">
|
||||
<search><![CDATA[$country_info = $this->model_localisation_country->getCountry($this->request->post['country_id']);]]></search>
|
||||
<add position="before"><![CDATA[
|
||||
if ($this->model_account_address->hasPOBoxFacility($this->request->post['postcode'], $this->request->post['address_1'])) {
|
||||
$json['error']['address_1'] = $this->language->get('error_po_box');
|
||||
}
|
||||
]]></add>
|
||||
</operation>
|
||||
</file>
|
||||
|
||||
<file path="catalog/controller/checkout/guest.php">
|
||||
<operation error="log">
|
||||
<search><![CDATA[if ($this->request->post['country_id'] == '') {]]></search>
|
||||
<add position="before"><![CDATA[
|
||||
$this->load->model('account/address');
|
||||
if ($this->model_account_address->hasPOBoxFacility($this->request->post['postcode'], $this->request->post['address_1'])) {
|
||||
$json['error']['address_1'] = $this->language->get('error_po_box');
|
||||
}
|
||||
]]></add>
|
||||
</operation>
|
||||
</file>
|
||||
|
||||
<file path="catalog/language/*/checkout/checkout.php">
|
||||
<operation error="skip">
|
||||
<search><![CDATA[$_['error_address_1'] = 'Address 1 must be between 3 and 128 characters!';]]></search>
|
||||
<add position="after"><![CDATA[
|
||||
$_['error_po_box'] = 'PO Box addresses are not allowed.';
|
||||
]]></add>
|
||||
</operation>
|
||||
</file>
|
||||
|
||||
<file path="catalog/language/*/account/address.php">
|
||||
<operation error="skip">
|
||||
<search><![CDATA[$_['error_address_1'] = 'Address must be between 3 and 128 characters!';]]></search>
|
||||
<add position="after"><![CDATA[
|
||||
$_['error_po_box'] = 'PO Box addresses are not allowed.';
|
||||
]]></add>
|
||||
</operation>
|
||||
</file>
|
||||
|
||||
<file path="">
|
||||
<operation error="skip">
|
||||
<search><![CDATA[]]></search>
|
||||
|
||||
Reference in New Issue
Block a user