Osclass stands apart in the world of classifieds software due to its deliberate simplicity and raw power. As a standalone application built with pure, framework-free PHP, it offers developers an unparalleled level of control and performance, unburdened by the overhead of a multi-purpose CMS. This direct access, however, is governed by a single, unbreakable rule: Never, ever hack the core.
This in-depth technical guide is for PHP developers ready to master the Osclass platform the right way. We will dissect the three pillars of professional Osclass development: leveraging the powerful Hooks system for seamless interaction, building Child Themes for complete visual control, and engineering robust Plugins to introduce new functionality. By mastering these concepts, you can build any type of marketplace imaginable while ensuring your platform remains secure, stable, and easily upgradeable.
Before you write a single line of code, this principle must be understood. Modifying Osclass core files (any file within the oc-admin or oc-includes directories) is a critical error that leads to three disastrous outcomes:
The entire Osclass architecture is designed to be extended safely and efficiently through its APIs, primarily using the powerful system of **Hooks**.
The Hooks system is the central nervous system of Osclass development. It allows your custom code to interact with the Osclass core at hundreds of specific points without ever modifying a core file. It's directly inspired by the WordPress Hooks system and consists of two distinct types: **Actions** and **Filters**.
Actions are specific events that occur during the Osclass execution lifecycle (e.g., header, posted_item, user_register_completed). When Osclass reaches one of these points, it triggers the hook, checks if any functions are "registered" to it, and executes them in order of priority.
You use the function osc_add_hook() to attach your custom function to an action.
osc_add_hook(string $hook_name, callable $function_name, int $priority = 10);
This is a classic "Hello World" for hooks. Instead of editing footer.php, you hook into the footer action.
// In your plugin's main file or your theme's functions.php
function my_custom_tracking_script() {
echo '<!-- Custom Google Tag Manager Code -->' . PHP_EOL;
echo "<script>console.log('Page loaded, tracking initialized.');</script>" . PHP_EOL;
}
osc_add_hook('footer', 'my_custom_tracking_script');
Let's create a more advanced function that logs the title of every new listing to a text file. We'll use the posted_item hook, which conveniently passes the complete item data array to our function.
function log_new_listing_title($item) {
// The $item array contains all data for the newly posted item.
// Let's get the title and the ID.
$itemTitle = $item['s_title'];
$itemId = $item['pk_i_id'];
$logMessage = date('Y-m-d H:i:s') . " - New Listing Published (ID: " . $itemId . "): " . $itemTitle . PHP_EOL;
// Define the path to our log file in a writable directory
$logFile = osc_content_path() . 'logs/new_listings.log';
// Use file_put_contents with the FILE_APPEND flag to add to the log
@file_put_contents($logFile, $logMessage, FILE_APPEND);
}
osc_add_hook('posted_item', 'log_new_listing_title');
While there are hundreds of hooks, here are some of the most critical for developers:
init (runs early), before_html (before any HTML output), after_html (after all HTML output).header (in <head>), admin_header, footer (before </body>), admin_footer.pre_item_add (before adding to DB), posted_item (after adding), pre_item_edit (before editing), edited_item (after editing), delete_item (passes $itemId), item_form (to add fields to the publish form), item_detail (to add content to the listing page).before_user_register (before registration), user_register_completed (after registration, passes $userId), delete_user (passes $userId), profile_form (to add fields to the user profile).search_form (to add fields to the search form).Filters are even more powerful than actions. They give you the ability to intercept data, modify it, and then return it before it's used by Osclass (either for display or for saving to the database). The most important rule of filters is that your hooked function must always return a value.
You use osc_add_filter() to attach your function to a filter.
osc_add_filter(string $filter_name, callable $function_name, int $priority = 10);
A simple example to illustrate the concept. We intercept the title, add our text, and return it.
function append_text_to_title($title) {
// IMPORTANT: Always return the modified (or original) variable
return $title . ' - For Sale';
}
osc_add_filter('item_title', 'append_text_to_title');
The correct way to validate or modify input before it's saved to the database is to use an early action hook like pre_item_add or pre_item_edit to check the submitted parameters.
function validate_minimum_price() {
$price = Params::getParam('price');
$min_price = 5000000; // Price is stored in millionths ($5.00)
// Check if a price was submitted and if it's below the minimum
if ($price !== '' && $price < $min_price) {
// Add a flash message to inform the user
osc_add_flash_error_message('The minimum price is $5. Please enter a higher value.');
// Redirect back to the form. For the publish form:
osc_redirect_to(osc_item_post_url());
}
}
osc_add_hook('pre_item_add', 'validate_minimum_price');
osc_add_hook('pre_item_edit', 'validate_minimum_price');
The search_conditions filter is extremely powerful. It lets you modify the array of WHERE conditions for the item search SQL query. Let's exclude category ID 99 (e.g., "Archived Items") from all public searches.
function exclude_category_from_search($conditions) {
// Add our custom WHERE clause to the array of conditions
$conditions[] = DB_TABLE_PREFIX . 't_item.fk_i_category_id <> 99';
return $conditions;
}
osc_add_filter('search_conditions', 'exclude_category_from_search');
meta_title, meta_description, canonical_url.item_title, item_description, item_price.item_edit_prepare (modify item data before it populates the edit form).search_conditions (modify WHERE clause), search_order (modify ORDER BY), search_sql (modify the entire SQL query).email_user_registration_subject, email_user_registration_description (and many others for every email template).The theme controls 100% of the HTML output of your Osclass site. A deep understanding of its structure is key to creating a unique user experience.
As with the core, you should never directly edit a theme you didn't create. Always create a Child Theme. This allows you to update the parent theme (to get bug fixes and new features) without losing your customizations.
To create a child theme for the default "Bender" theme:
oc-content/themes/bender-child/.index.php with this header:
<?php
/*
Theme Name: Bender Child
Template: bender
*/
?>
The Template: bender line is the magic that links it to the parent.functions.php file in your child theme folder. This is where you can add your custom hooks and functions.To modify a template file (e.g., item.php), simply copy it from the parent (bender/item.php) to your child theme (bender-child/item.php) and edit it there. Osclass will automatically use your child theme's version.
The "Loop" is the standard mechanism Osclass uses to display a list of items on a search page. Understanding it is fundamental to theme development.
<?php if (osc_has_items()) { ?>
<div class="listings">
<?php while (osc_has_items()) { ?>
<div class="listing-item">
<h3><a href="<?php echo osc_item_url(); ?>"><?php echo osc_item_title(); ?></a></h3>
<p class="price"><?php echo osc_item_formatted_price(); ?></p>
<p class="location"><?php echo osc_item_city(); ?>, <?php echo osc_item_region(); ?></p>
<?php if(osc_count_item_resources() > 0) { ?>
<a href="<?php echo osc_item_url(); ?>">
<img src="<?php echo osc_resource_thumbnail_url(); ?>" alt="<?php echo osc_esc_html(osc_item_title()); ?>" loading="lazy">
</a>
<?php } ?>
</div>
<?php } ?>
</div>
<?php if (osc_search_total_pages() > 1) { ?>
<div class="pagination">
<?php echo osc_search_pagination(); ?>
</div>
<?php } ?>
<?php } else { ?>
<p class="empty-search">No listings found.</p>
<?php } ?>
When you need to add new, self-contained functionality that is independent of the visual theme, a plugin is the correct tool.
A professional plugin cleans up after itself. Osclass provides hooks to manage the plugin's lifecycle.
// This code runs when the user activates the plugin
function my_plugin_activate() {
// Example: Create a new database table
$conn = DBConnectionClass::newInstance();
$conn->dao->query('CREATE TABLE IF NOT EXISTS ' . DB_TABLE_PREFIX . 't_my_plugin_log (...)');
// Example: Set a default preference
osc_set_preference('my_plugin_version', '1.0.0', 'my_plugin_settings', 'STRING');
}
osc_register_plugin(osc_plugin_path(__FILE__), 'my_plugin_activate');
// This code runs when the user deactivates the plugin
function my_plugin_deactivate() {
// Example: Delete the preference
osc_delete_preference('my_plugin_version', 'my_plugin_settings');
}
osc_add_hook(osc_plugin_path(__FILE__) . '_disable', 'my_plugin_deactivate');
// This code runs when the user uninstalls the plugin
function my_plugin_uninstall() {
// Example: Drop the custom database table
$conn = DBConnectionClass::newInstance();
$conn->dao->query('DROP TABLE IF EXISTS ' . DB_TABLE_PREFIX . 't_my_plugin_log');
}
osc_add_hook(osc_plugin_path(__FILE__) . '_uninstall', 'my_plugin_uninstall');
A plugin with options needs a settings page in the admin panel.
// In your plugin's main file, hooked to 'admin_menu_init'
function my_plugin_admin_menu() {
osc_add_admin_menu_page(
'My Plugin Settings', // Page Title
osc_admin_render_plugin_url(osc_plugin_folder(__FILE__) . 'admin.php'), // URL to your settings file
'my_plugin_settings', // Unique ID
'plugins' // Parent Menu (plugins, settings, etc.)
);
}
osc_add_hook('admin_menu_init', 'my_plugin_admin_menu');
// Then, create admin.php in your plugin folder to render the HTML form.
// In that file, you would use osc_set_preference() to save form data.
Beyond hooks and presentation, a powerful plugin or theme often needs to directly interact with the Osclass database, retrieve specific information, handle user input, and communicate back to the user. This section covers the essential APIs and helper functions you'll use every day to build dynamic and interactive features.
Osclass provides a database abstraction layer to ensure that all database queries are handled securely and consistently. You should **never** use raw mysqli_* or PDO functions. Instead, you must use the Osclass Data Access Object (DAO). This provides a simple way to build queries and automatically handles prepared statements to prevent SQL injection.
To get started, you first need to get an instance of the connection object.
$conn = DBConnectionClass::newInstance();
$dao = $conn->getDao();
The DAO provides several methods to retrieve data. The most common is query() for custom selects, and findByPrimaryKey() for getting a single record by its ID.
Example: Get the details of the 5 most recent listings.
// Get the DAO instance
$dao = new DAO();
// Use the Item DAO to get the specific model for items
// This provides useful constants and table names
$itemDao = Item::newInstance();
// Build and execute the query
$dao->select('i.*, d.*');
$dao->from($itemDao->getTableName() . ' as i');
$dao->join($itemDao->getTableDescription() . ' as d', 'i.pk_i_id = d.fk_i_item_id');
$dao->where('d.fk_c_locale_code', osc_current_user_locale());
$dao->where('i.b_enabled', 1);
$dao->where('i.b_active', 1);
$dao->orderBy('i.dt_pub_date', 'DESC');
$dao->limit(5);
$result = $dao->get();
// The result is an object. To get an array of items:
$items = $result->result();
if (!empty($items)) {
echo '<ul>';
foreach ($items as $item) {
// The item array contains all database fields for that listing
echo '<li>(' . $item['pk_i_id'] . ') ' . $item['s_title'] . '</li>';
}
echo '</ul>';
}
When your plugin needs its own database table, you'll use the insert() method. It takes the table name and an array of key-value pairs ('column_name' => 'value').
Example: Log a search query to a custom plugin table.
// Assume you created a table named 't_my_plugin_searches' during plugin activation
$searchLogTable = DB_TABLE_PREFIX . 't_my_plugin_searches';
$dataToInsert = array(
's_query' => Params::getParam('sPattern'), // Get search query safely
'dt_date' => date('Y-m-d H:i:s'),
'fk_i_user_id' => osc_logged_user_id() // Returns user ID or null
);
// Get the DAO and perform the insert
$dao = new DAO();
$success = $dao->insert($searchLogTable, $dataToInsert);
if ($success) {
// The query was successful
} else {
// There was an error
}
The update() method is used to modify existing records. It requires the table name, an array of data to update, and an array specifying the WHERE clause.
Example: Add a "view count" to your custom search log table.
$searchLogTable = DB_TABLE_PREFIX . 't_my_plugin_searches';
$logIdToUpdate = 123; // The primary key of the record to update
// We need to increment the existing view count
$dao = new DAO();
$dao->update($searchLogTable, array('i_views = i_views + 1'), array('pk_i_id' => $logIdToUpdate));
The delete() method removes records. It takes the table name and a WHERE clause array.
Example: Delete old log entries from your custom table.
$searchLogTable = DB_TABLE_PREFIX . 't_my_plugin_searches';
// Delete all logs older than 30 days
$dao = new DAO();
$success = $dao->delete($searchLogTable, "dt_date < DATE_SUB(NOW(), INTERVAL 30 DAY)");
Osclass provides hundreds of global "helper" functions that handle the logic of retrieving and formatting data for display. You should **always** use these helpers in your themes and plugins instead of querying the database directly and formatting the data yourself. This ensures your code remains compatible with future Osclass updates.
These functions are only available within the "View" context. This means they work automatically in theme files. If you need to use them inside a function in a plugin, you must first get the View object.
$view = View::newInstance();
These functions work when you are inside the Osclass Loop (while (osc_has_items()) { ... }) on a search page, or on an item page. They automatically refer to the current listing being displayed.
osc_item_id(): Returns the integer ID of the item.osc_item_title(): Returns the sanitized title of the item.osc_item_description(): Returns the sanitized description.osc_item_formatted_price(): Returns the price, correctly formatted with currency symbol and decimal places.osc_item_pub_date(): Returns the publication date, formatted according to your site's settings.osc_item_city(), osc_item_region(), osc_item_country(): Returns location details.osc_item_url(): Returns the full, SEO-friendly URL to the listing.osc_count_item_resources(): Returns the number of images/media attached to the listing.These functions help you retrieve information about the currently logged-in user or the user who posted a listing.
osc_is_web_user_logged_in(): Returns true or false. Essential for conditional logic.osc_logged_user_id(): Returns the integer ID of the logged-in user.osc_logged_user_name(): Returns the name of the logged-in user.osc_logged_user_email(): Returns the email of the logged-in user.osc_user_id(): Inside the loop or on an item page, returns the ID of the item's author.osc_user_name(): Inside the loop or on an item page, returns the name of the item's author.Never hardcode URLs in your themes or plugins. Always use URL helpers to generate them dynamically. This ensures your links will work even if you change your site's URL structure.
osc_base_url(): Returns the base URL of your site.osc_contact_url(): Returns the URL of the contact page.osc_user_dashboard_url(): Returns the URL to the logged-in user's dashboard.osc_user_login_url(): Returns the URL of the login page.osc_user_register_url(): Returns the URL of the registration page.When creating a settings page for a plugin or a custom form for users, you must handle the input securely. Osclass provides tools to help with this.
Cross-Site Request Forgery (CSRF) is a common vulnerability. Osclass has a built-in system to prevent it. You must include a CSRF token in all your forms.
<form action="<?php echo osc_admin_render_plugin_url(osc_plugin_folder(__FILE__) . 'admin.php'); ?>" method="post">
<!-- IMPORTANT: This hidden input is for security -->
<input type="hidden" name="action_specific" value="save_my_settings" />
<?php AdminForm::generate_csrf_token(); ?>
<h2>My Plugin Settings</h2>
<label for="apiKey">API Key</label>
<input type="text" name="apiKey" id="apiKey" value="<?php echo osc_esc_html(osc_get_preference('api_key', 'my_plugin_settings')); ?>" />
<button type="submit">Save Settings</button>
</form>
In your processing file (my_plugin/admin.php in this case), you must verify the CSRF token and use the Params class to retrieve the submitted data. **Never use $_POST or $_GET directly.** The Params class automatically runs sanitization routines on the input.
<?php
// First, check if the form was submitted with our specific action
if (Params::getParam('action_specific') == 'save_my_settings') {
// SECOND, verify the CSRF token to prevent unauthorized submissions
AdminForm::is_csrf_token_valid();
// THIRD, get the submitted data using the Params class
$apiKey = Params::getParam('apiKey', false, false); // Params::getParam(key, xss_check, quotes_check)
// FOURTH, save the data using the Preferences API
osc_set_preference('api_key', $apiKey, 'my_plugin_settings', 'STRING');
// FIFTH, provide feedback to the user and redirect
osc_add_flash_ok_message('Your settings have been saved successfully.', 'admin');
osc_redirect_to(osc_admin_render_plugin_url(osc_plugin_folder(__FILE__) . 'admin.php'));
}
?>
Flash messages are temporary notifications displayed to the user after they perform an action (e.g., "Your listing has been published," "Settings saved," "Invalid email address").
You can set flash messages from anywhere in your plugin or theme's functions. They are stored in the session and displayed on the next page load.
osc_add_flash_ok_message('Success! Your profile was updated.') - For success (green).osc_add_flash_info_message('Your subscription is expiring in 7 days.') - For information (blue).osc_add_flash_warning_message('Please review your listing before publishing.') - For warnings (yellow).osc_add_flash_error_message('The password you entered was incorrect.') - For errors (red).To actually show the messages to the user, you need to include the generic message template in your theme files (e.g., in header.php or main.php).
<?php osc_show_flash_message(); ?>
This single function will render any pending flash messages with the correct styling and then clear them from the session so they don't appear again.
With the fundamentals of hooks, themes, and database interaction covered, we can now explore more advanced techniques that are essential for building a modern, dynamic, and professional Osclass platform. This section will cover implementing AJAX, making your extensions translatable (Internationalization), working with custom fields, and creating unique URL routes.
Asynchronous JavaScript and XML (AJAX) allows you to update parts of a webpage without needing to reload the entire page. This is crucial for features like live search, contact forms, or adding an item to a "favorites" list. Osclass has a built-in AJAX handler that makes this process secure and standardized.
The process involves three key steps: the JavaScript request, hooking into the Osclass AJAX API, and the PHP handler function.
First, you need to write the JavaScript that sends the request. We'll use jQuery, which is included with Osclass. The critical part is passing a security token to verify the request is legitimate.
Example: A "Favorite this Item" button in item.php.
<!-- In your theme's item.php file -->
<button class="add-to-favorites" data-item-id="<?php echo osc_item_id(); ?>">Add to Favorites</button>
// In your theme's main javascript file
$(document).ready(function(){
$('.add-to-favorites').on('click', function(e){
e.preventDefault();
var button = $(this);
var itemId = button.data('item-id');
// The AJAX URL needs a custom action name
var ajaxUrl = '<?php echo osc_ajax_hook_url('my_plugin_favorite_item'); ?>';
$.ajax({
url: ajaxUrl,
type: 'POST',
data: {
itemId: itemId
},
dataType: 'json',
success: function(response) {
if(response.success) {
button.text('Favorited!').prop('disabled', true);
alert(response.message);
} else {
alert('Error: ' + response.message);
}
},
error: function() {
alert('An unexpected error occurred. Please try again.');
}
});
});
});
Osclass listens for AJAX calls using a specific action hook format: osc_ajax_{your_action_name}. The your_action_name must match what you used to generate the AJAX URL in your JavaScript.
// In your plugin's main file or your theme's functions.php
osc_add_hook('osc_ajax_my_plugin_favorite_item', 'my_plugin_handle_favorite_request');
This is the PHP function that will process the request. It must perform its logic and then echo a JSON-encoded response before terminating the script.
function my_plugin_handle_favorite_request() {
// Get the data from the request
$itemId = Params::getParam('itemId');
$userId = osc_logged_user_id();
// Perform your business logic
if (!$userId) {
echo json_encode(['success' => false, 'message' => 'You must be logged in to favorite items.']);
die();
}
if ($itemId > 0) {
// Here, you would add your database logic to save the favorite.
// For example: Favorites::newInstance()->add($userId, $itemId);
$response = [
'success' => true,
'message' => 'Item successfully added to your favorites!'
];
} else {
$response = [
'success' => false,
'message' => 'Invalid Item ID provided.'
];
}
// Echo the response and terminate the script
header('Content-Type: application/json');
echo json_encode($response);
die();
}
If you plan to share your plugin or theme, it is essential to make it translatable. This process, called Internationalization (i18n), involves wrapping all human-readable strings in your code with special Gettext functions. This allows other users to create language files (.po and .mo) to translate your extension into their own language.
Osclass provides several helper functions that are wrappers for the standard PHP Gettext extension.
__(): Use this when you need to **return** a translatable string (e.g., assign it to a variable)._e(): Use this when you need to **echo** a translatable string directly to the browser.Example: Making a simple string translatable.
<?php
// --- INCORRECT (Hardcoded string) ---
$my_variable = 'Hello World';
echo '<h2>My Plugin Settings</h2>';
// --- CORRECT (Translatable strings) ---
// Use __() to return the string to a variable
$my_variable = __('Hello World', 'my_plugin_domain');
// Use _e() to echo the string directly
echo '<h2>';
_e('My Plugin Settings', 'my_plugin_domain');
echo '</h2>';
?>
The second argument, 'my_plugin_domain', is the "text domain." It's a unique identifier for your plugin or theme that tells Osclass which language file to load the translation from.
Once your code is prepared with Gettext functions, you can use a program like Poedit to scan your plugin's folder. It will find all the translatable strings and generate a .pot (Portable Object Template) file. You can then translate this file into other languages, creating .po and .mo files for each one, which should be placed in your plugin's languages subfolder.
Custom fields are one of Osclass's most powerful features for creating a niche marketplace. Once you've created a custom field in the admin panel (e.g., a "Mileage" field for cars), you need to know how to display its value in your theme.
Osclass provides a simple loop to iterate through all the custom fields associated with a listing. This should be used within your theme's item.php file.
<?php if (osc_has_custom_fields()) { ?>
<div class="item-custom-fields">
<h3>Additional Details</h3>
<ul>
<?php while (osc_has_custom_fields()) { ?>
<?php if (osc_field_value() != '') { ?>
<li>
<strong><?php echo osc_field_name(); ?>:</strong>
<?php echo osc_field_value(); ?>
</li>
<?php } ?>
<?php } ?>
</ul>
</div>
<?php } ?>
Sometimes a plugin needs its own custom, user-facing URL that doesn't follow the standard Osclass structure (e.g., your-site.com/my-plugin/dashboard/). Osclass has a routing system that lets you define custom URL rules and map them to a specific PHP file in your plugin.
You register a new route using osc_add_route(). This is typically done from your plugin's main file.
function my_plugin_register_routes() {
osc_add_route(
'my_plugin_dashboard', // Unique Route Name
'my-plugin/dashboard/?', // The URL Regex Rule
'my-plugin/dashboard/', // The "pretty" URL to rewrite to
osc_plugin_folder(__FILE__) . 'views/user_dashboard.php' // The plugin file to load
);
}
// You can hook this into 'init'
osc_add_hook('init', 'my_plugin_register_routes');
Now, when a user visits https://your-site.com/my-plugin/dashboard/, Osclass will load the content from the user_dashboard.php file located in your plugin's views folder. This allows you to create complex, multi-page plugins with clean, SEO-friendly URLs.
A truly professional Osclass plugin or theme doesn't just add front-end features; it integrates seamlessly into the Osclass administration panel. This provides a polished user experience for the site owner and elevates your extension from a simple script to a complete solution. This section covers advanced techniques for creating admin dashboard widgets, extending the core "Manage Items" table, leveraging Osclass's data storage APIs, and creating custom email notifications.
The Osclass admin dashboard is widget-based, allowing users to customize the information they see at a glance. Your plugin can register its own widgets to display important statistics, recent activity, or quick action links. This is achieved by creating a widget class and registering it with Osclass.
You must tell Osclass about your new widget. This is done by hooking into the widgets_init action and calling osc_register_widget() from your plugin's main file.
// In your plugin's main file
function my_plugin_register_widgets() {
require_once osc_plugin_path(__FILE__) . 'widgets/LatestUnapprovedWidget.php';
osc_register_widget('LatestUnapprovedWidget');
}
osc_add_hook('widgets_init', 'my_plugin_register_widgets');
Now, create the actual widget file (widgets/LatestUnapprovedWidget.php). The class must extend the AdminWidget base class and implement a render() method. This method contains the logic to fetch and display the widget's content.
<?php
class LatestUnapprovedWidget extends AdminWidget {
public function __construct() {
parent::__construct(
'latest_unapproved_widget', // Unique widget ID
__('Latest Unapproved Items', 'my_plugin_domain'), // Widget Name
__('Displays a list of the 5 most recent listings awaiting approval.', 'my_plugin_domain') // Widget Description
);
}
/**
* The main function that renders the widget's HTML content.
*/
public function render() {
// Use the Item DAO to find items that are not active and not enabled
$items = Item::newInstance()->find(
array(
'b_active' => false,
'b_enabled' => false
),
0, // Start from the first record
5, // Limit to 5 results
'dt_pub_date', // Order by
'DESC' // Order direction
);
echo '<div class="widget-latest-unapproved">';
if (count($items) > 0) {
echo '<ul>';
foreach ($items as $item) {
$url = osc_admin_base_url(true) . '?page=items&action=item_edit&id=' . $item['pk_i_id'];
echo '<li>';
echo '<a href="' . $url . '" target="_blank">' . osc_esc_html($item['s_title']) . '</a>';
echo ' <span style="color:#999;">(' . osc_format_date($item['dt_pub_date']) . ')</span>';
echo '</li>';
}
echo '</ul>';
} else {
echo '<p>' . __('No items are currently awaiting approval.', 'my_plugin_domain') . '</p>';
}
echo '</div>';
}
}
?>
Once this is done, the site administrator can go to the dashboard, click "Add Widget," and find "Latest Unapproved Items" in the list of available widgets.
One of the most powerful ways to improve an admin's workflow is to add custom, relevant information directly to the main listings table at Tools > Items. You can add new columns with custom data and even add new bulk actions.
This is a two-step process: first, you add the column header, and second, you populate the column's content for each row.
// In your plugin's main file
// Step 1: Add the column header
function my_plugin_add_item_table_header($columns) {
// Add our new column at the 3rd position (index 2)
array_splice($columns, 2, 0, array('my_custom_column' => __('My Custom Data', 'my_plugin_domain')));
return $columns;
}
osc_add_filter('manage_items_columns', 'my_plugin_add_item_table_header');
// Step 2: Populate the column for each item row
function my_plugin_add_item_table_content($item) {
// The $item array contains all data for the current row
// Check if we are in our custom column
if (osc_current_admin_column() === 'my_custom_column') {
// Example: Get a custom meta field value for this item
$my_data = osc_get_item_meta('my_plugin_custom_field');
echo ($my_data != '') ? osc_esc_html($my_data) : 'N/A';
}
}
osc_add_hook('items_processing_row', 'my_plugin_add_item_table_content');
This allows an admin to select multiple listings and apply a custom action to all of them at once.
// In your plugin's main file
// Step 1: Add the option to the dropdown menu
function my_plugin_add_bulk_action($actions) {
$actions['my_custom_action'] = __('Mark as Special', 'my_plugin_domain');
return $actions;
}
osc_add_filter('item_bulk_actions', 'my_plugin_add_bulk_action');
// Step 2: Process the action when the form is submitted
function my_plugin_handle_bulk_action($action, $itemIds) {
if ($action === 'my_custom_action') {
$count = 0;
foreach ($itemIds as $id) {
// Your logic here: update a custom field, send an API call, etc.
// For example, let's update a meta field for each item
Item::newInstance()->update(array('b_special' => 1), array('pk_i_id' => $id));
$count++;
}
osc_add_flash_ok_message($count . ' ' . __('items have been marked as special.', 'my_plugin_domain'), 'admin');
}
}
// Note: This hook receives the action name and an array of selected item IDs
osc_add_hook('item_bulk_action', 'my_plugin_handle_bulk_action', 10, 2);
Osclass provides two primary mechanisms for storing data: the **Session** for temporary, user-specific data, and **Preferences** for permanent, site-wide settings.
The Session is for data that should only persist for a single user's visit. It's perfect for multi-step forms or storing temporary user choices. Always use the Session class wrapper.
// Get the Session instance
$session = Session::newInstance();
// Store a value in the session
$session->_set('my_plugin_user_choice', 'blue');
// Retrieve a value from the session
$userColor = $session->_get('my_plugin_user_choice'); // Returns 'blue'
// Check if a session variable exists
if ($session->_is_set('my_plugin_user_choice')) {
// ...
}
// Remove a value from the session
$session->_drop('my_plugin_user_choice');
Preferences are stored permanently in the t_preference database table. This is the correct way to store your plugin's settings. The API handles serialization and caching automatically.
// To save a preference
// osc_set_preference(key, value, section, type)
// 'section' should be a unique name for your plugin to avoid conflicts
osc_set_preference('api_key', 'xyz123abc', 'my_plugin_settings', 'STRING');
osc_set_preference('enable_feature', true, 'my_plugin_settings', 'BOOLEAN');
osc_set_preference('item_limit', 50, 'my_plugin_settings', 'INTEGER');
// To retrieve a preference
$apiKey = osc_get_preference('api_key', 'my_plugin_settings');
$isFeatureEnabled = (bool) osc_get_preference('enable_feature', 'my_plugin_settings');
// To delete a preference
osc_delete_preference('api_key', 'my_plugin_settings');
A professional plugin often needs to send its own unique email notifications. Osclass has a robust, template-based email system that you should always use instead of calling PHP's mail() function directly. This ensures emails are themed correctly and can be translated by the user.
First, you need to add your email's content to the database so the admin can edit it.
function my_plugin_activate() {
$email_data = array(
's_name' => 'My Plugin Notification',
's_internal_name' => 'my_plugin_custom_email', // Unique internal name
's_title' => 'Hello, {CONTACT_NAME}! A special event has occurred.',
's_text' => '<p>Dear {CONTACT_NAME},</p><p>We are writing to inform you that the listing "{ITEM_TITLE}" has been flagged for review.</p><p>Thank you,<br>{SITE_TITLE}</p>'
);
// Use the EmailTemplates model to insert it
EmailTemplates::newInstance()->insert($email_data);
}
osc_register_plugin(osc_plugin_path(__FILE__), 'my_plugin_activate');
To send the email, you gather your data, prepare your placeholders (like {CONTACT_NAME}), and then use osc_send_mail().
function send_my_custom_notification($itemId) {
// Get the item and user data
$item = Item::newInstance()->findByPrimaryKey($itemId);
$user = User::newInstance()->findByPrimaryKey($item['fk_i_user_id']);
// Get the email template from the database
$email_template = EmailTemplates::newInstance()->findByInternalName('my_plugin_custom_email');
// Prepare the placeholders to be replaced
$placeholders = array(
'{CONTACT_NAME}' => $user['s_name'],
'{ITEM_TITLE}' => $item['s_title'],
'{SITE_TITLE}' => osc_page_title(),
'{SITE_URL}' => osc_base_url()
);
// Replace placeholders in the subject and body
$subject = osc_apply_placeholders($email_template['s_title'], $placeholders);
$body = osc_apply_placeholders($email_template['s_text'], $placeholders);
// Create the email parameters array
$email_params = array(
'to' => $user['s_email'],
'to_name' => $user['s_name'],
'subject' => $subject,
'body' => $body
);
// Send the email using the Osclass mailer
osc_send_mail($email_params);
}
Mastering the APIs is the first step; becoming a professional Osclass developer requires adopting patterns that ensure your extensions are robust, secure, and seamlessly integrated. This final section moves beyond individual functions to explore higher-level concepts: leveraging the Osclass Model layer for elegant data manipulation, implementing advanced security practices, automating tasks with the Command Line Interface (CLI), and creating front-end widgets that empower site administrators.
While the Data Access Object (DAO) is excellent for custom SQL queries, Osclass is built on a Model-View-Controller (MVC) architecture. The **Models** (e.g., Item, User, Category) are the heart of this pattern. They contain the business logic and pre-built methods for common data operations. Using Models instead of the DAO for standard tasks leads to cleaner, more readable, and more maintainable code.
DAO vs. Model: A Practical Comparison
Imagine you need to fetch all active listings for a specific user (ID 123).
The DAO approach (more verbose):
$dao = new DAO();
$dao->select();
$dao->from(DB_TABLE_PREFIX . 't_item');
$dao->where('fk_i_user_id', 123);
$dao->where('b_active', 1);
$dao->where('b_enabled', 1);
$result = $dao->get();
$items = $result->result();
The Model approach (cleaner and more abstract):
// The Item model has a built-in method for this exact task
$items = Item::newInstance()->findByUserID(123);
The Model approach is not only shorter but also less prone to error, as the complex query logic is handled internally by the Osclass core. You should always check the relevant Model file in oc-includes/osclass/model/ to see if a method already exists for your needs before writing a custom DAO query.
Item::newInstance()->findLatest($count): Gets the $count most recently published items.Item::newInstance()->findPopular($count): Gets the $count most viewed items.User::newInstance()->findByEmail($email): Finds a user by their email address.Category::newInstance()->findSubcategories($categoryId): Gets all direct subcategories of a given category ID.Alerts::newInstance()->findSubscribers($itemId): Finds all users who have an active search alert that matches a newly published item.Writing secure code is non-negotiable. While the Osclass core provides a secure foundation, a poorly written plugin can expose a website to significant risk. Go beyond the basics of CSRF nonces with these critical practices.
These two concepts are often confused. Sanitization (like using Params::getParam()) cleans data. Validation confirms data is what you expect it to be. You must do both.
Example: Validating a user-submitted age field.
// Get the sanitized input
$age = Params::getParam('user_age');
// Now, VALIDATE it
if (!is_numeric($age) || $age < 18 || $age > 120) {
// The data is not a valid age, even if it's sanitized.
// Return an error and do not process it.
osc_add_flash_error_message('Please enter a valid age between 18 and 120.');
// Redirect back to the form...
} else {
// Validation passed, proceed to save the integer value.
User::newInstance()->update(array('i_age' => (int)$age), array('pk_i_id' => osc_logged_user_id()));
}
Never trust data, even data from your own database. It could have been compromised or entered maliciously. You must "escape" all data just before you echo it to the browser to prevent malicious scripts from running.
osc_esc_html($string): Use this for echoing content inside a standard HTML element (e.g., <div>, <p>, <strong>). This is the most common escaping function.osc_esc_js($string): Use this when echoing a string inside a JavaScript block (e.g., in an alert() or when defining a variable).esc_attr($string): A WordPress-compatible function for echoing content inside an HTML attribute (e.g., title="..." or placeholder="...").<?php $listingTitle = osc_item_title(); // Gets the raw title ?>
<!-- CORRECT: Escaped for HTML context -->
<h2 title="<?php echo esc_attr($listingTitle); ?>"><?php echo osc_esc_html($listingTitle); ?></h2>
<script>
// CORRECT: Escaped for JavaScript context
var itemTitle = '<?php echo osc_esc_js($listingTitle); ?>';
console.log('The title of this item is: ' + itemTitle);
</script>
Never assume a user has the right to perform an action. Always check their permissions, especially for admin-side functionality.
osc_is_admin_user_logged_in(): Returns true if the current user is a logged-in administrator.osc_is_web_user_logged_in(): Returns true if the current user is a logged-in front-end user.osc_item_user_id(): Returns the ID of the user who published the current item. You can compare this to osc_logged_user_id() to see if the current user is the owner of the listing.Many marketplaces require automated, recurring tasks, such as deactivating expired listings, sending out daily email digests, or cleaning up temporary files. Osclass includes a Command Line Interface (CLI) entry point, cron.php, which can be triggered by a server cron job to run these tasks.
You can create a function that performs your desired task and hook it into one of Osclass's built-in schedules: cron_hourly, cron_daily, or cron_weekly.
// In your plugin's main file
// This function will contain the logic for our automated task
function my_plugin_deactivate_old_listings() {
$conn = DBConnectionClass::newInstance();
$dao = $conn->getDao();
// Deactivate all items older than 90 days that have not been renewed
$dao->update(
Item::newInstance()->getTableName(),
array('b_active' => 0),
"dt_pub_date < DATE_SUB(NOW(), INTERVAL 90 DAY)"
);
// You could also log that the cron ran successfully
error_log('My Plugin: Daily deactivation cron ran successfully.');
}
// Attach our function to the built-in daily cron hook
osc_add_hook('cron_daily', 'my_plugin_deactivate_old_listings');
To run all daily cron hooks, you would set up a cron job on your server (via cPanel or the command line) to execute the following command once per day:
php /path/to/your/osclass/cron.php --cron-type=daily
This command will trigger the cron_daily hook in Osclass, which in turn will execute your my_plugin_deactivate_old_listings function and any other functions attached to that hook.
Plugins can provide their own front-end widgets that can be placed in a theme's sidebar or footer. This allows your plugin's functionality to be visually integrated into any theme.
You must include your widget file and then register it using osc_register_widget().
// In your plugin's main file
function my_plugin_register_frontend_widgets() {
require_once osc_plugin_path(__FILE__) . 'widgets/TopViewedWidget.php';
osc_register_widget('TopViewedWidget');
}
// A good place to call this is in the main plugin file or hooked into 'init'.
osc_add_hook('init', 'my_plugin_register_frontend_widgets');
The front-end widget class must extend the Widget base class and contain a widget() function. This function is responsible for rendering the widget's HTML.
<?php
class TopViewedWidget extends Widget {
public function __construct() {
$this->setAsciiName('top_viewed_widget');
$this->setName(__('Top 5 Most Viewed Items', 'my_plugin_domain'));
$this->setDescription(__('Displays a list of the 5 most popular listings on the site.', 'my_plugin_domain'));
}
/**
* The main function that renders the widget's HTML content.
* @param array $params Contains any options set by the user in the admin panel.
*/
public function widget($params = array()) {
// Use the Item model to get the 5 most popular items
$popularItems = Item::newInstance()->findPopular(5);
echo '<div class="widget widget-top-viewed">';
echo '<h3>' . $this->getName() . '</h3>';
if (count($popularItems) > 0) {
echo '<ul>';
foreach ($popularItems as $item) {
// To use item helpers here, we need to manually set the view context
View::newInstance()->_exportVariableToView('item', $item);
echo '<li>';
echo '<a href="' . osc_item_url() . '">' . osc_item_title() . '</a>';
echo ' <span>(' . osc_item_views() . ' views)</span>';
echo '</li>';
}
echo '</ul>';
} else {
echo '<p>' . __('No listings have been viewed yet.', 'my_plugin_domain') . '</p>';
}
echo '</div>';
}
}
?>
Once registered, administrators can go to Appearance > Widgets, drag the "Top 5 Most Viewed Items" widget into a sidebar, and it will appear on the front end of their site.