From 94608c02a0661a969c099714cf86697d16528ea6 Mon Sep 17 00:00:00 2001 From: Mohamed Youssef Date: Fri, 31 Jul 2026 17:26:38 +0300 Subject: [PATCH] init --- README.md | 69 ++++++--------- install.xml | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 258 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 4234d9a..adc17ba 100644 --- a/README.md +++ b/README.md @@ -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 ``, ``, and `` - - Add your modifications in the `` 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 ``, ``, and `` + - 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. diff --git a/install.xml b/install.xml index 5a69cd6..dfb1c01 100644 --- a/install.xml +++ b/install.xml @@ -1,11 +1,242 @@ - - + Prevent PO Box Addresses + mydev_prevent_po_box_addresses MY-Dev | Mohamed Youssef]]> https://my-dev.pro 1.0.0 + + + + $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; + } + ]]> + + + + + + request->post['country_id'] == '') {]]> + model_account_address->hasPOBoxFacility($this->request->post['postcode'], $this->request->post['address_1'])) { + $json['error']['address_1'] = $this->language->get('error_po_box'); + } + ]]> + + + + + + model_localisation_country->getCountry($this->request->post['country_id']);]]> + model_account_address->hasPOBoxFacility($this->request->post['postcode'], $this->request->post['address_1'])) { + $json['error']['address_1'] = $this->language->get('error_po_box'); + } + ]]> + + + + + + request->post['country_id'] == '') {]]> + 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'); + } + ]]> + + + + + + + + + + + + + + + + +