@mytechtoday/augment-extensions 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/augment-extensions/domain-rules/wordpress/README.md +163 -0
- package/augment-extensions/domain-rules/wordpress/module.json +32 -0
- package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
- package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
- package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
- package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
- package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
- package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
- package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
- package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
- package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
- package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
- package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
- package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
- package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
- package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
- package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
- package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
- package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
- package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
- package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
- package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
- package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
- package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
- package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
- package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
- package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
- package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
- package/cli/dist/cli.js +5 -1
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/show.d.ts.map +1 -1
- package/cli/dist/commands/show.js +41 -0
- package/cli/dist/commands/show.js.map +1 -1
- package/modules.md +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1727 @@
|
|
|
1
|
+
# Custom Post Type Plugin Example
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This example demonstrates a complete **Portfolio Custom Post Type Plugin** with custom taxonomy, meta boxes, archive/single templates, and shortcode display.
|
|
6
|
+
|
|
7
|
+
**Complexity**: Medium
|
|
8
|
+
**File Count**: 10-15 files
|
|
9
|
+
**Team Size**: 1-2 developers
|
|
10
|
+
**Use Case**: Portfolio sites, project showcases, case studies, team members, testimonials
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Complete Plugin: "Portfolio Manager"
|
|
15
|
+
|
|
16
|
+
A comprehensive portfolio management plugin demonstrating custom post types, taxonomies, meta boxes, templates, and frontend display.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Directory Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
portfolio-manager/
|
|
24
|
+
├── portfolio-manager.php # Main plugin file
|
|
25
|
+
├── uninstall.php # Uninstall cleanup
|
|
26
|
+
├── readme.txt # WordPress.org readme
|
|
27
|
+
├── includes/
|
|
28
|
+
│ ├── class-portfolio-post-type.php # CPT registration
|
|
29
|
+
│ ├── class-portfolio-taxonomy.php # Taxonomy registration
|
|
30
|
+
│ ├── class-portfolio-meta-boxes.php # Meta boxes
|
|
31
|
+
│ └── class-portfolio-shortcodes.php # Shortcodes
|
|
32
|
+
├── admin/
|
|
33
|
+
│ ├── css/
|
|
34
|
+
│ │ └── admin.css # Admin styles
|
|
35
|
+
│ └── js/
|
|
36
|
+
│ └── admin.js # Admin scripts
|
|
37
|
+
├── public/
|
|
38
|
+
│ ├── css/
|
|
39
|
+
│ │ └── portfolio.css # Frontend styles
|
|
40
|
+
│ ├── js/
|
|
41
|
+
│ │ └── portfolio.js # Frontend scripts
|
|
42
|
+
│ └── templates/
|
|
43
|
+
│ ├── archive-portfolio.php # Archive template
|
|
44
|
+
│ ├── single-portfolio.php # Single template
|
|
45
|
+
│ └── content-portfolio.php # Content template
|
|
46
|
+
└── assets/
|
|
47
|
+
└── images/
|
|
48
|
+
└── placeholder.png # Placeholder image
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Main Plugin File
|
|
54
|
+
|
|
55
|
+
### File: `portfolio-manager.php`
|
|
56
|
+
|
|
57
|
+
```php
|
|
58
|
+
<?php
|
|
59
|
+
/**
|
|
60
|
+
* Plugin Name: Portfolio Manager
|
|
61
|
+
* Plugin URI: https://example.com/portfolio-manager
|
|
62
|
+
* Description: Complete portfolio management with custom post type, taxonomy, meta boxes, and templates
|
|
63
|
+
* Version: 1.0.0
|
|
64
|
+
* Author: Your Name
|
|
65
|
+
* Author URI: https://example.com
|
|
66
|
+
* License: GPL-2.0+
|
|
67
|
+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
|
68
|
+
* Text Domain: portfolio-manager
|
|
69
|
+
* Domain Path: /languages
|
|
70
|
+
*
|
|
71
|
+
* @package Portfolio_Manager
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
// Exit if accessed directly
|
|
75
|
+
if (!defined('ABSPATH')) {
|
|
76
|
+
exit;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Define plugin constants
|
|
80
|
+
define('PM_VERSION', '1.0.0');
|
|
81
|
+
define('PM_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
|
82
|
+
define('PM_PLUGIN_URL', plugin_dir_url(__FILE__));
|
|
83
|
+
define('PM_PLUGIN_FILE', __FILE__);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Autoloader
|
|
87
|
+
*/
|
|
88
|
+
spl_autoload_register(function ($class) {
|
|
89
|
+
$prefix = 'Portfolio_';
|
|
90
|
+
|
|
91
|
+
if (strpos($class, $prefix) !== 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$class_name = str_replace($prefix, '', $class);
|
|
96
|
+
$class_file = 'class-' . str_replace('_', '-', strtolower($class_name)) . '.php';
|
|
97
|
+
|
|
98
|
+
$file = PM_PLUGIN_DIR . 'includes/' . $class_file;
|
|
99
|
+
if (file_exists($file)) {
|
|
100
|
+
require $file;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Activation hook
|
|
106
|
+
*/
|
|
107
|
+
register_activation_hook(__FILE__, function() {
|
|
108
|
+
// Register post type and taxonomy
|
|
109
|
+
Portfolio_Post_Type::register();
|
|
110
|
+
Portfolio_Taxonomy::register();
|
|
111
|
+
|
|
112
|
+
// Flush rewrite rules
|
|
113
|
+
flush_rewrite_rules();
|
|
114
|
+
|
|
115
|
+
// Set default options
|
|
116
|
+
add_option('pm_archive_columns', 3);
|
|
117
|
+
add_option('pm_items_per_page', 12);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Deactivation hook
|
|
122
|
+
*/
|
|
123
|
+
register_deactivation_hook(__FILE__, function() {
|
|
124
|
+
// Flush rewrite rules
|
|
125
|
+
flush_rewrite_rules();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Initialize the plugin
|
|
130
|
+
*/
|
|
131
|
+
function run_portfolio_manager() {
|
|
132
|
+
// Register post type
|
|
133
|
+
Portfolio_Post_Type::init();
|
|
134
|
+
|
|
135
|
+
// Register taxonomy
|
|
136
|
+
Portfolio_Taxonomy::init();
|
|
137
|
+
|
|
138
|
+
// Register meta boxes
|
|
139
|
+
Portfolio_Meta_Boxes::init();
|
|
140
|
+
|
|
141
|
+
// Register shortcodes
|
|
142
|
+
Portfolio_Shortcodes::init();
|
|
143
|
+
|
|
144
|
+
// Load text domain
|
|
145
|
+
add_action('plugins_loaded', 'pm_load_textdomain');
|
|
146
|
+
|
|
147
|
+
// Enqueue assets
|
|
148
|
+
add_action('wp_enqueue_scripts', 'pm_enqueue_public_assets');
|
|
149
|
+
add_action('admin_enqueue_scripts', 'pm_enqueue_admin_assets');
|
|
150
|
+
|
|
151
|
+
// Template includes
|
|
152
|
+
add_filter('template_include', 'pm_template_include');
|
|
153
|
+
}
|
|
154
|
+
add_action('plugins_loaded', 'run_portfolio_manager');
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Load plugin text domain
|
|
158
|
+
*/
|
|
159
|
+
function pm_load_textdomain() {
|
|
160
|
+
load_plugin_textdomain(
|
|
161
|
+
'portfolio-manager',
|
|
162
|
+
false,
|
|
163
|
+
dirname(plugin_basename(PM_PLUGIN_FILE)) . '/languages/'
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Enqueue public assets
|
|
169
|
+
*/
|
|
170
|
+
function pm_enqueue_public_assets() {
|
|
171
|
+
if (is_post_type_archive('portfolio') || is_singular('portfolio') || is_tax('portfolio_category')) {
|
|
172
|
+
wp_enqueue_style(
|
|
173
|
+
'portfolio-manager',
|
|
174
|
+
PM_PLUGIN_URL . 'public/css/portfolio.css',
|
|
175
|
+
array(),
|
|
176
|
+
PM_VERSION
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
wp_enqueue_script(
|
|
180
|
+
'portfolio-manager',
|
|
181
|
+
PM_PLUGIN_URL . 'public/js/portfolio.js',
|
|
182
|
+
array('jquery'),
|
|
183
|
+
PM_VERSION,
|
|
184
|
+
true
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Enqueue admin assets
|
|
191
|
+
*/
|
|
192
|
+
function pm_enqueue_admin_assets($hook) {
|
|
193
|
+
global $post_type;
|
|
194
|
+
|
|
195
|
+
if ('portfolio' === $post_type) {
|
|
196
|
+
wp_enqueue_style(
|
|
197
|
+
'portfolio-manager-admin',
|
|
198
|
+
PM_PLUGIN_URL . 'admin/css/admin.css',
|
|
199
|
+
array(),
|
|
200
|
+
PM_VERSION
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
wp_enqueue_script(
|
|
204
|
+
'portfolio-manager-admin',
|
|
205
|
+
PM_PLUGIN_URL . 'admin/js/admin.js',
|
|
206
|
+
array('jquery'),
|
|
207
|
+
PM_VERSION,
|
|
208
|
+
true
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Enqueue media uploader
|
|
212
|
+
wp_enqueue_media();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Template include
|
|
218
|
+
*/
|
|
219
|
+
function pm_template_include($template) {
|
|
220
|
+
if (is_post_type_archive('portfolio')) {
|
|
221
|
+
$plugin_template = PM_PLUGIN_DIR . 'public/templates/archive-portfolio.php';
|
|
222
|
+
if (file_exists($plugin_template)) {
|
|
223
|
+
return $plugin_template;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (is_singular('portfolio')) {
|
|
228
|
+
$plugin_template = PM_PLUGIN_DIR . 'public/templates/single-portfolio.php';
|
|
229
|
+
if (file_exists($plugin_template)) {
|
|
230
|
+
return $plugin_template;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return $template;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Custom Post Type Registration
|
|
241
|
+
|
|
242
|
+
### File: `includes/class-portfolio-post-type.php`
|
|
243
|
+
|
|
244
|
+
```php
|
|
245
|
+
<?php
|
|
246
|
+
/**
|
|
247
|
+
* Portfolio custom post type
|
|
248
|
+
*
|
|
249
|
+
* @package Portfolio_Manager
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
class Portfolio_Post_Type {
|
|
253
|
+
/**
|
|
254
|
+
* Initialize
|
|
255
|
+
*/
|
|
256
|
+
public static function init() {
|
|
257
|
+
add_action('init', array(__CLASS__, 'register'));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Register custom post type
|
|
262
|
+
*/
|
|
263
|
+
public static function register() {
|
|
264
|
+
$labels = array(
|
|
265
|
+
'name' => __('Portfolio Items', 'portfolio-manager'),
|
|
266
|
+
'singular_name' => __('Portfolio Item', 'portfolio-manager'),
|
|
267
|
+
'add_new' => __('Add New', 'portfolio-manager'),
|
|
268
|
+
'add_new_item' => __('Add New Portfolio Item', 'portfolio-manager'),
|
|
269
|
+
'edit_item' => __('Edit Portfolio Item', 'portfolio-manager'),
|
|
270
|
+
'new_item' => __('New Portfolio Item', 'portfolio-manager'),
|
|
271
|
+
'view_item' => __('View Portfolio Item', 'portfolio-manager'),
|
|
272
|
+
'view_items' => __('View Portfolio Items', 'portfolio-manager'),
|
|
273
|
+
'search_items' => __('Search Portfolio', 'portfolio-manager'),
|
|
274
|
+
'not_found' => __('No portfolio items found', 'portfolio-manager'),
|
|
275
|
+
'not_found_in_trash' => __('No portfolio items found in Trash', 'portfolio-manager'),
|
|
276
|
+
'all_items' => __('All Portfolio Items', 'portfolio-manager'),
|
|
277
|
+
'archives' => __('Portfolio Archives', 'portfolio-manager'),
|
|
278
|
+
'attributes' => __('Portfolio Attributes', 'portfolio-manager'),
|
|
279
|
+
'insert_into_item' => __('Insert into portfolio item', 'portfolio-manager'),
|
|
280
|
+
'uploaded_to_this_item' => __('Uploaded to this portfolio item', 'portfolio-manager'),
|
|
281
|
+
'menu_name' => __('Portfolio', 'portfolio-manager'),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
$args = array(
|
|
285
|
+
'labels' => $labels,
|
|
286
|
+
'public' => true,
|
|
287
|
+
'publicly_queryable' => true,
|
|
288
|
+
'show_ui' => true,
|
|
289
|
+
'show_in_menu' => true,
|
|
290
|
+
'show_in_nav_menus' => true,
|
|
291
|
+
'show_in_admin_bar' => true,
|
|
292
|
+
'query_var' => true,
|
|
293
|
+
'rewrite' => array(
|
|
294
|
+
'slug' => 'portfolio',
|
|
295
|
+
'with_front' => false,
|
|
296
|
+
'feeds' => true,
|
|
297
|
+
'pages' => true,
|
|
298
|
+
),
|
|
299
|
+
'capability_type' => 'post',
|
|
300
|
+
'has_archive' => true,
|
|
301
|
+
'hierarchical' => false,
|
|
302
|
+
'menu_position' => 20,
|
|
303
|
+
'menu_icon' => 'dashicons-portfolio',
|
|
304
|
+
'supports' => array(
|
|
305
|
+
'title',
|
|
306
|
+
'editor',
|
|
307
|
+
'thumbnail',
|
|
308
|
+
'excerpt',
|
|
309
|
+
'revisions',
|
|
310
|
+
),
|
|
311
|
+
'show_in_rest' => true,
|
|
312
|
+
'rest_base' => 'portfolio',
|
|
313
|
+
'rest_controller_class' => 'WP_REST_Posts_Controller',
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
register_post_type('portfolio', $args);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Custom Taxonomy Registration
|
|
324
|
+
|
|
325
|
+
### File: `includes/class-portfolio-taxonomy.php`
|
|
326
|
+
|
|
327
|
+
```php
|
|
328
|
+
<?php
|
|
329
|
+
/**
|
|
330
|
+
* Portfolio taxonomy
|
|
331
|
+
*
|
|
332
|
+
* @package Portfolio_Manager
|
|
333
|
+
*/
|
|
334
|
+
|
|
335
|
+
class Portfolio_Taxonomy {
|
|
336
|
+
/**
|
|
337
|
+
* Initialize
|
|
338
|
+
*/
|
|
339
|
+
public static function init() {
|
|
340
|
+
add_action('init', array(__CLASS__, 'register'));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Register taxonomy
|
|
345
|
+
*/
|
|
346
|
+
public static function register() {
|
|
347
|
+
$labels = array(
|
|
348
|
+
'name' => __('Portfolio Categories', 'portfolio-manager'),
|
|
349
|
+
'singular_name' => __('Portfolio Category', 'portfolio-manager'),
|
|
350
|
+
'search_items' => __('Search Categories', 'portfolio-manager'),
|
|
351
|
+
'all_items' => __('All Categories', 'portfolio-manager'),
|
|
352
|
+
'parent_item' => __('Parent Category', 'portfolio-manager'),
|
|
353
|
+
'parent_item_colon' => __('Parent Category:', 'portfolio-manager'),
|
|
354
|
+
'edit_item' => __('Edit Category', 'portfolio-manager'),
|
|
355
|
+
'update_item' => __('Update Category', 'portfolio-manager'),
|
|
356
|
+
'add_new_item' => __('Add New Category', 'portfolio-manager'),
|
|
357
|
+
'new_item_name' => __('New Category Name', 'portfolio-manager'),
|
|
358
|
+
'menu_name' => __('Categories', 'portfolio-manager'),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
$args = array(
|
|
362
|
+
'labels' => $labels,
|
|
363
|
+
'hierarchical' => true,
|
|
364
|
+
'public' => true,
|
|
365
|
+
'show_ui' => true,
|
|
366
|
+
'show_admin_column' => true,
|
|
367
|
+
'show_in_nav_menus' => true,
|
|
368
|
+
'show_tagcloud' => true,
|
|
369
|
+
'query_var' => true,
|
|
370
|
+
'rewrite' => array(
|
|
371
|
+
'slug' => 'portfolio-category',
|
|
372
|
+
'with_front' => false,
|
|
373
|
+
'hierarchical' => true,
|
|
374
|
+
),
|
|
375
|
+
'show_in_rest' => true,
|
|
376
|
+
'rest_base' => 'portfolio_categories',
|
|
377
|
+
'rest_controller_class' => 'WP_REST_Terms_Controller',
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
register_taxonomy('portfolio_category', array('portfolio'), $args);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Meta Boxes
|
|
388
|
+
|
|
389
|
+
### File: `includes/class-portfolio-meta-boxes.php`
|
|
390
|
+
|
|
391
|
+
```php
|
|
392
|
+
<?php
|
|
393
|
+
/**
|
|
394
|
+
* Portfolio meta boxes
|
|
395
|
+
*
|
|
396
|
+
* @package Portfolio_Manager
|
|
397
|
+
*/
|
|
398
|
+
|
|
399
|
+
class Portfolio_Meta_Boxes {
|
|
400
|
+
/**
|
|
401
|
+
* Initialize
|
|
402
|
+
*/
|
|
403
|
+
public static function init() {
|
|
404
|
+
add_action('add_meta_boxes', array(__CLASS__, 'add_meta_boxes'));
|
|
405
|
+
add_action('save_post_portfolio', array(__CLASS__, 'save_meta_boxes'), 10, 2);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Add meta boxes
|
|
410
|
+
*/
|
|
411
|
+
public static function add_meta_boxes() {
|
|
412
|
+
add_meta_box(
|
|
413
|
+
'portfolio_details',
|
|
414
|
+
__('Portfolio Details', 'portfolio-manager'),
|
|
415
|
+
array(__CLASS__, 'render_details_meta_box'),
|
|
416
|
+
'portfolio',
|
|
417
|
+
'normal',
|
|
418
|
+
'high'
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
add_meta_box(
|
|
422
|
+
'portfolio_gallery',
|
|
423
|
+
__('Portfolio Gallery', 'portfolio-manager'),
|
|
424
|
+
array(__CLASS__, 'render_gallery_meta_box'),
|
|
425
|
+
'portfolio',
|
|
426
|
+
'normal',
|
|
427
|
+
'default'
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Render details meta box
|
|
433
|
+
*/
|
|
434
|
+
public static function render_details_meta_box($post) {
|
|
435
|
+
// Nonce field for security
|
|
436
|
+
wp_nonce_field('portfolio_details_nonce', 'portfolio_details_nonce_field');
|
|
437
|
+
|
|
438
|
+
// Get current values
|
|
439
|
+
$client = get_post_meta($post->ID, '_portfolio_client', true);
|
|
440
|
+
$url = get_post_meta($post->ID, '_portfolio_url', true);
|
|
441
|
+
$date = get_post_meta($post->ID, '_portfolio_date', true);
|
|
442
|
+
$technologies = get_post_meta($post->ID, '_portfolio_technologies', true);
|
|
443
|
+
|
|
444
|
+
?>
|
|
445
|
+
<table class="form-table">
|
|
446
|
+
<tr>
|
|
447
|
+
<th><label for="portfolio_client"><?php _e('Client Name', 'portfolio-manager'); ?></label></th>
|
|
448
|
+
<td>
|
|
449
|
+
<input type="text" id="portfolio_client" name="portfolio_client"
|
|
450
|
+
value="<?php echo esc_attr($client); ?>" class="regular-text">
|
|
451
|
+
<p class="description"><?php _e('Name of the client or company', 'portfolio-manager'); ?></p>
|
|
452
|
+
</td>
|
|
453
|
+
</tr>
|
|
454
|
+
<tr>
|
|
455
|
+
<th><label for="portfolio_url"><?php _e('Project URL', 'portfolio-manager'); ?></label></th>
|
|
456
|
+
<td>
|
|
457
|
+
<input type="url" id="portfolio_url" name="portfolio_url"
|
|
458
|
+
value="<?php echo esc_url($url); ?>" class="regular-text">
|
|
459
|
+
<p class="description"><?php _e('Live project URL', 'portfolio-manager'); ?></p>
|
|
460
|
+
</td>
|
|
461
|
+
</tr>
|
|
462
|
+
<tr>
|
|
463
|
+
<th><label for="portfolio_date"><?php _e('Completion Date', 'portfolio-manager'); ?></label></th>
|
|
464
|
+
<td>
|
|
465
|
+
<input type="date" id="portfolio_date" name="portfolio_date"
|
|
466
|
+
value="<?php echo esc_attr($date); ?>">
|
|
467
|
+
<p class="description"><?php _e('Project completion date', 'portfolio-manager'); ?></p>
|
|
468
|
+
</td>
|
|
469
|
+
</tr>
|
|
470
|
+
<tr>
|
|
471
|
+
<th><label for="portfolio_technologies"><?php _e('Technologies Used', 'portfolio-manager'); ?></label></th>
|
|
472
|
+
<td>
|
|
473
|
+
<textarea id="portfolio_technologies" name="portfolio_technologies"
|
|
474
|
+
rows="3" class="large-text"><?php echo esc_textarea($technologies); ?></textarea>
|
|
475
|
+
<p class="description"><?php _e('Comma-separated list of technologies', 'portfolio-manager'); ?></p>
|
|
476
|
+
</td>
|
|
477
|
+
</tr>
|
|
478
|
+
</table>
|
|
479
|
+
<?php
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Render gallery meta box
|
|
484
|
+
*/
|
|
485
|
+
public static function render_gallery_meta_box($post) {
|
|
486
|
+
// Nonce field for security
|
|
487
|
+
wp_nonce_field('portfolio_gallery_nonce', 'portfolio_gallery_nonce_field');
|
|
488
|
+
|
|
489
|
+
// Get current gallery
|
|
490
|
+
$gallery = get_post_meta($post->ID, '_portfolio_gallery', true);
|
|
491
|
+
$gallery_ids = !empty($gallery) ? explode(',', $gallery) : array();
|
|
492
|
+
|
|
493
|
+
?>
|
|
494
|
+
<div class="portfolio-gallery-container">
|
|
495
|
+
<div class="portfolio-gallery-images">
|
|
496
|
+
<?php if (!empty($gallery_ids)): ?>
|
|
497
|
+
<?php foreach ($gallery_ids as $image_id): ?>
|
|
498
|
+
<?php $image = wp_get_attachment_image_src($image_id, 'thumbnail'); ?>
|
|
499
|
+
<div class="portfolio-gallery-image" data-id="<?php echo esc_attr($image_id); ?>">
|
|
500
|
+
<img src="<?php echo esc_url($image[0]); ?>" alt="">
|
|
501
|
+
<button type="button" class="remove-image">×</button>
|
|
502
|
+
</div>
|
|
503
|
+
<?php endforeach; ?>
|
|
504
|
+
<?php endif; ?>
|
|
505
|
+
</div>
|
|
506
|
+
<input type="hidden" id="portfolio_gallery" name="portfolio_gallery"
|
|
507
|
+
value="<?php echo esc_attr($gallery); ?>">
|
|
508
|
+
<button type="button" class="button portfolio-add-images">
|
|
509
|
+
<?php _e('Add Images', 'portfolio-manager'); ?>
|
|
510
|
+
</button>
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
<script>
|
|
514
|
+
jQuery(document).ready(function($) {
|
|
515
|
+
var frame;
|
|
516
|
+
|
|
517
|
+
// Add images
|
|
518
|
+
$('.portfolio-add-images').on('click', function(e) {
|
|
519
|
+
e.preventDefault();
|
|
520
|
+
|
|
521
|
+
if (frame) {
|
|
522
|
+
frame.open();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
frame = wp.media({
|
|
527
|
+
title: '<?php _e('Select Portfolio Images', 'portfolio-manager'); ?>',
|
|
528
|
+
button: {
|
|
529
|
+
text: '<?php _e('Add to Gallery', 'portfolio-manager'); ?>'
|
|
530
|
+
},
|
|
531
|
+
multiple: true
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
frame.on('select', function() {
|
|
535
|
+
var selection = frame.state().get('selection');
|
|
536
|
+
var ids = $('#portfolio_gallery').val().split(',').filter(Boolean);
|
|
537
|
+
|
|
538
|
+
selection.each(function(attachment) {
|
|
539
|
+
attachment = attachment.toJSON();
|
|
540
|
+
ids.push(attachment.id);
|
|
541
|
+
|
|
542
|
+
$('.portfolio-gallery-images').append(
|
|
543
|
+
'<div class="portfolio-gallery-image" data-id="' + attachment.id + '">' +
|
|
544
|
+
'<img src="' + attachment.sizes.thumbnail.url + '" alt="">' +
|
|
545
|
+
'<button type="button" class="remove-image">×</button>' +
|
|
546
|
+
'</div>'
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
$('#portfolio_gallery').val(ids.join(','));
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
frame.open();
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Remove image
|
|
557
|
+
$(document).on('click', '.remove-image', function(e) {
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
var $image = $(this).closest('.portfolio-gallery-image');
|
|
560
|
+
var id = $image.data('id');
|
|
561
|
+
var ids = $('#portfolio_gallery').val().split(',').filter(Boolean);
|
|
562
|
+
|
|
563
|
+
ids = ids.filter(function(item) {
|
|
564
|
+
return item != id;
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
$('#portfolio_gallery').val(ids.join(','));
|
|
568
|
+
$image.remove();
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
</script>
|
|
572
|
+
<?php
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Save meta boxes
|
|
577
|
+
*/
|
|
578
|
+
public static function save_meta_boxes($post_id, $post) {
|
|
579
|
+
// Check autosave
|
|
580
|
+
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Check permissions
|
|
585
|
+
if (!current_user_can('edit_post', $post_id)) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Save details
|
|
590
|
+
if (isset($_POST['portfolio_details_nonce_field']) &&
|
|
591
|
+
wp_verify_nonce($_POST['portfolio_details_nonce_field'], 'portfolio_details_nonce')) {
|
|
592
|
+
|
|
593
|
+
if (isset($_POST['portfolio_client'])) {
|
|
594
|
+
update_post_meta($post_id, '_portfolio_client', sanitize_text_field($_POST['portfolio_client']));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (isset($_POST['portfolio_url'])) {
|
|
598
|
+
update_post_meta($post_id, '_portfolio_url', esc_url_raw($_POST['portfolio_url']));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (isset($_POST['portfolio_date'])) {
|
|
602
|
+
update_post_meta($post_id, '_portfolio_date', sanitize_text_field($_POST['portfolio_date']));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (isset($_POST['portfolio_technologies'])) {
|
|
606
|
+
update_post_meta($post_id, '_portfolio_technologies', sanitize_textarea_field($_POST['portfolio_technologies']));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Save gallery
|
|
611
|
+
if (isset($_POST['portfolio_gallery_nonce_field']) &&
|
|
612
|
+
wp_verify_nonce($_POST['portfolio_gallery_nonce_field'], 'portfolio_gallery_nonce')) {
|
|
613
|
+
|
|
614
|
+
if (isset($_POST['portfolio_gallery'])) {
|
|
615
|
+
$gallery = sanitize_text_field($_POST['portfolio_gallery']);
|
|
616
|
+
update_post_meta($post_id, '_portfolio_gallery', $gallery);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Shortcodes
|
|
626
|
+
|
|
627
|
+
### File: `includes/class-portfolio-shortcodes.php`
|
|
628
|
+
|
|
629
|
+
```php
|
|
630
|
+
<?php
|
|
631
|
+
/**
|
|
632
|
+
* Portfolio shortcodes
|
|
633
|
+
*
|
|
634
|
+
* @package Portfolio_Manager
|
|
635
|
+
*/
|
|
636
|
+
|
|
637
|
+
class Portfolio_Shortcodes {
|
|
638
|
+
/**
|
|
639
|
+
* Initialize
|
|
640
|
+
*/
|
|
641
|
+
public static function init() {
|
|
642
|
+
add_shortcode('portfolio', array(__CLASS__, 'portfolio_shortcode'));
|
|
643
|
+
add_shortcode('portfolio_grid', array(__CLASS__, 'portfolio_grid_shortcode'));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Portfolio shortcode
|
|
648
|
+
* Usage: [portfolio limit="6" category="web-design" columns="3"]
|
|
649
|
+
*/
|
|
650
|
+
public static function portfolio_shortcode($atts) {
|
|
651
|
+
$atts = shortcode_atts(array(
|
|
652
|
+
'limit' => 6,
|
|
653
|
+
'category' => '',
|
|
654
|
+
'columns' => 3,
|
|
655
|
+
'orderby' => 'date',
|
|
656
|
+
'order' => 'DESC',
|
|
657
|
+
), $atts, 'portfolio');
|
|
658
|
+
|
|
659
|
+
// Query args
|
|
660
|
+
$args = array(
|
|
661
|
+
'post_type' => 'portfolio',
|
|
662
|
+
'posts_per_page' => intval($atts['limit']),
|
|
663
|
+
'orderby' => sanitize_text_field($atts['orderby']),
|
|
664
|
+
'order' => sanitize_text_field($atts['order']),
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
// Add category filter
|
|
668
|
+
if (!empty($atts['category'])) {
|
|
669
|
+
$args['tax_query'] = array(
|
|
670
|
+
array(
|
|
671
|
+
'taxonomy' => 'portfolio_category',
|
|
672
|
+
'field' => 'slug',
|
|
673
|
+
'terms' => sanitize_text_field($atts['category']),
|
|
674
|
+
),
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
$query = new WP_Query($args);
|
|
679
|
+
|
|
680
|
+
if (!$query->have_posts()) {
|
|
681
|
+
return '<p>' . __('No portfolio items found.', 'portfolio-manager') . '</p>';
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
$columns = intval($atts['columns']);
|
|
685
|
+
$column_class = 'portfolio-col-' . $columns;
|
|
686
|
+
|
|
687
|
+
ob_start();
|
|
688
|
+
?>
|
|
689
|
+
<div class="portfolio-grid <?php echo esc_attr($column_class); ?>">
|
|
690
|
+
<?php while ($query->have_posts()): $query->the_post(); ?>
|
|
691
|
+
<div class="portfolio-item">
|
|
692
|
+
<?php if (has_post_thumbnail()): ?>
|
|
693
|
+
<div class="portfolio-thumbnail">
|
|
694
|
+
<a href="<?php the_permalink(); ?>">
|
|
695
|
+
<?php the_post_thumbnail('medium'); ?>
|
|
696
|
+
</a>
|
|
697
|
+
</div>
|
|
698
|
+
<?php endif; ?>
|
|
699
|
+
|
|
700
|
+
<div class="portfolio-content">
|
|
701
|
+
<h3 class="portfolio-title">
|
|
702
|
+
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
|
|
703
|
+
</h3>
|
|
704
|
+
|
|
705
|
+
<?php
|
|
706
|
+
$categories = get_the_terms(get_the_ID(), 'portfolio_category');
|
|
707
|
+
if ($categories && !is_wp_error($categories)):
|
|
708
|
+
?>
|
|
709
|
+
<div class="portfolio-categories">
|
|
710
|
+
<?php foreach ($categories as $category): ?>
|
|
711
|
+
<span class="portfolio-category"><?php echo esc_html($category->name); ?></span>
|
|
712
|
+
<?php endforeach; ?>
|
|
713
|
+
</div>
|
|
714
|
+
<?php endif; ?>
|
|
715
|
+
|
|
716
|
+
<div class="portfolio-excerpt">
|
|
717
|
+
<?php the_excerpt(); ?>
|
|
718
|
+
</div>
|
|
719
|
+
|
|
720
|
+
<a href="<?php the_permalink(); ?>" class="portfolio-link">
|
|
721
|
+
<?php _e('View Project', 'portfolio-manager'); ?>
|
|
722
|
+
</a>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
<?php endwhile; ?>
|
|
726
|
+
</div>
|
|
727
|
+
<?php
|
|
728
|
+
wp_reset_postdata();
|
|
729
|
+
|
|
730
|
+
return ob_get_clean();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Portfolio grid shortcode with filtering
|
|
735
|
+
* Usage: [portfolio_grid]
|
|
736
|
+
*/
|
|
737
|
+
public static function portfolio_grid_shortcode($atts) {
|
|
738
|
+
$atts = shortcode_atts(array(
|
|
739
|
+
'limit' => -1,
|
|
740
|
+
'columns' => 3,
|
|
741
|
+
), $atts, 'portfolio_grid');
|
|
742
|
+
|
|
743
|
+
// Get all categories
|
|
744
|
+
$categories = get_terms(array(
|
|
745
|
+
'taxonomy' => 'portfolio_category',
|
|
746
|
+
'hide_empty' => true,
|
|
747
|
+
));
|
|
748
|
+
|
|
749
|
+
// Query all portfolio items
|
|
750
|
+
$args = array(
|
|
751
|
+
'post_type' => 'portfolio',
|
|
752
|
+
'posts_per_page' => intval($atts['limit']),
|
|
753
|
+
'orderby' => 'date',
|
|
754
|
+
'order' => 'DESC',
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
$query = new WP_Query($args);
|
|
758
|
+
|
|
759
|
+
if (!$query->have_posts()) {
|
|
760
|
+
return '<p>' . __('No portfolio items found.', 'portfolio-manager') . '</p>';
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
$columns = intval($atts['columns']);
|
|
764
|
+
$column_class = 'portfolio-col-' . $columns;
|
|
765
|
+
|
|
766
|
+
ob_start();
|
|
767
|
+
?>
|
|
768
|
+
<div class="portfolio-filterable">
|
|
769
|
+
<?php if (!empty($categories) && !is_wp_error($categories)): ?>
|
|
770
|
+
<div class="portfolio-filters">
|
|
771
|
+
<button class="portfolio-filter active" data-filter="*">
|
|
772
|
+
<?php _e('All', 'portfolio-manager'); ?>
|
|
773
|
+
</button>
|
|
774
|
+
<?php foreach ($categories as $category): ?>
|
|
775
|
+
<button class="portfolio-filter" data-filter=".cat-<?php echo esc_attr($category->slug); ?>">
|
|
776
|
+
<?php echo esc_html($category->name); ?>
|
|
777
|
+
</button>
|
|
778
|
+
<?php endforeach; ?>
|
|
779
|
+
</div>
|
|
780
|
+
<?php endif; ?>
|
|
781
|
+
|
|
782
|
+
<div class="portfolio-grid <?php echo esc_attr($column_class); ?>">
|
|
783
|
+
<?php while ($query->have_posts()): $query->the_post(); ?>
|
|
784
|
+
<?php
|
|
785
|
+
$item_categories = get_the_terms(get_the_ID(), 'portfolio_category');
|
|
786
|
+
$cat_classes = array();
|
|
787
|
+
if ($item_categories && !is_wp_error($item_categories)) {
|
|
788
|
+
foreach ($item_categories as $cat) {
|
|
789
|
+
$cat_classes[] = 'cat-' . $cat->slug;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
?>
|
|
793
|
+
<div class="portfolio-item <?php echo esc_attr(implode(' ', $cat_classes)); ?>">
|
|
794
|
+
<?php if (has_post_thumbnail()): ?>
|
|
795
|
+
<div class="portfolio-thumbnail">
|
|
796
|
+
<a href="<?php the_permalink(); ?>">
|
|
797
|
+
<?php the_post_thumbnail('medium'); ?>
|
|
798
|
+
</a>
|
|
799
|
+
</div>
|
|
800
|
+
<?php endif; ?>
|
|
801
|
+
|
|
802
|
+
<div class="portfolio-content">
|
|
803
|
+
<h3 class="portfolio-title">
|
|
804
|
+
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
|
|
805
|
+
</h3>
|
|
806
|
+
|
|
807
|
+
<?php if ($item_categories && !is_wp_error($item_categories)): ?>
|
|
808
|
+
<div class="portfolio-categories">
|
|
809
|
+
<?php foreach ($item_categories as $category): ?>
|
|
810
|
+
<span class="portfolio-category"><?php echo esc_html($category->name); ?></span>
|
|
811
|
+
<?php endforeach; ?>
|
|
812
|
+
</div>
|
|
813
|
+
<?php endif; ?>
|
|
814
|
+
</div>
|
|
815
|
+
</div>
|
|
816
|
+
<?php endwhile; ?>
|
|
817
|
+
</div>
|
|
818
|
+
</div>
|
|
819
|
+
<?php
|
|
820
|
+
wp_reset_postdata();
|
|
821
|
+
|
|
822
|
+
return ob_get_clean();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
## Template Files
|
|
830
|
+
|
|
831
|
+
### File: `public/templates/archive-portfolio.php`
|
|
832
|
+
|
|
833
|
+
```php
|
|
834
|
+
<?php
|
|
835
|
+
/**
|
|
836
|
+
* Archive template for portfolio
|
|
837
|
+
*
|
|
838
|
+
* @package Portfolio_Manager
|
|
839
|
+
*/
|
|
840
|
+
|
|
841
|
+
get_header();
|
|
842
|
+
?>
|
|
843
|
+
|
|
844
|
+
<div class="portfolio-archive">
|
|
845
|
+
<header class="page-header">
|
|
846
|
+
<h1 class="page-title"><?php post_type_archive_title(); ?></h1>
|
|
847
|
+
|
|
848
|
+
<?php
|
|
849
|
+
$description = get_the_archive_description();
|
|
850
|
+
if ($description):
|
|
851
|
+
?>
|
|
852
|
+
<div class="archive-description"><?php echo wp_kses_post($description); ?></div>
|
|
853
|
+
<?php endif; ?>
|
|
854
|
+
</header>
|
|
855
|
+
|
|
856
|
+
<?php
|
|
857
|
+
// Get categories for filtering
|
|
858
|
+
$categories = get_terms(array(
|
|
859
|
+
'taxonomy' => 'portfolio_category',
|
|
860
|
+
'hide_empty' => true,
|
|
861
|
+
));
|
|
862
|
+
|
|
863
|
+
if (!empty($categories) && !is_wp_error($categories)):
|
|
864
|
+
?>
|
|
865
|
+
<div class="portfolio-filters">
|
|
866
|
+
<button class="portfolio-filter active" data-filter="*">
|
|
867
|
+
<?php _e('All', 'portfolio-manager'); ?>
|
|
868
|
+
</button>
|
|
869
|
+
<?php foreach ($categories as $category): ?>
|
|
870
|
+
<button class="portfolio-filter" data-filter=".cat-<?php echo esc_attr($category->slug); ?>">
|
|
871
|
+
<?php echo esc_html($category->name); ?>
|
|
872
|
+
</button>
|
|
873
|
+
<?php endforeach; ?>
|
|
874
|
+
</div>
|
|
875
|
+
<?php endif; ?>
|
|
876
|
+
|
|
877
|
+
<?php if (have_posts()): ?>
|
|
878
|
+
<div class="portfolio-grid portfolio-col-3">
|
|
879
|
+
<?php while (have_posts()): the_post(); ?>
|
|
880
|
+
<?php get_template_part('public/templates/content', 'portfolio'); ?>
|
|
881
|
+
<?php endwhile; ?>
|
|
882
|
+
</div>
|
|
883
|
+
|
|
884
|
+
<?php
|
|
885
|
+
the_posts_pagination(array(
|
|
886
|
+
'mid_size' => 2,
|
|
887
|
+
'prev_text' => __('« Previous', 'portfolio-manager'),
|
|
888
|
+
'next_text' => __('Next »', 'portfolio-manager'),
|
|
889
|
+
));
|
|
890
|
+
?>
|
|
891
|
+
<?php else: ?>
|
|
892
|
+
<p><?php _e('No portfolio items found.', 'portfolio-manager'); ?></p>
|
|
893
|
+
<?php endif; ?>
|
|
894
|
+
</div>
|
|
895
|
+
|
|
896
|
+
<?php
|
|
897
|
+
get_footer();
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
### File: `public/templates/single-portfolio.php`
|
|
901
|
+
|
|
902
|
+
```php
|
|
903
|
+
<?php
|
|
904
|
+
/**
|
|
905
|
+
* Single template for portfolio
|
|
906
|
+
*
|
|
907
|
+
* @package Portfolio_Manager
|
|
908
|
+
*/
|
|
909
|
+
|
|
910
|
+
get_header();
|
|
911
|
+
?>
|
|
912
|
+
|
|
913
|
+
<div class="portfolio-single">
|
|
914
|
+
<?php while (have_posts()): the_post(); ?>
|
|
915
|
+
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
|
|
916
|
+
<header class="entry-header">
|
|
917
|
+
<h1 class="entry-title"><?php the_title(); ?></h1>
|
|
918
|
+
|
|
919
|
+
<?php
|
|
920
|
+
$categories = get_the_terms(get_the_ID(), 'portfolio_category');
|
|
921
|
+
if ($categories && !is_wp_error($categories)):
|
|
922
|
+
?>
|
|
923
|
+
<div class="portfolio-categories">
|
|
924
|
+
<?php foreach ($categories as $category): ?>
|
|
925
|
+
<a href="<?php echo esc_url(get_term_link($category)); ?>" class="portfolio-category">
|
|
926
|
+
<?php echo esc_html($category->name); ?>
|
|
927
|
+
</a>
|
|
928
|
+
<?php endforeach; ?>
|
|
929
|
+
</div>
|
|
930
|
+
<?php endif; ?>
|
|
931
|
+
</header>
|
|
932
|
+
|
|
933
|
+
<?php if (has_post_thumbnail()): ?>
|
|
934
|
+
<div class="portfolio-featured-image">
|
|
935
|
+
<?php the_post_thumbnail('large'); ?>
|
|
936
|
+
</div>
|
|
937
|
+
<?php endif; ?>
|
|
938
|
+
|
|
939
|
+
<div class="portfolio-details">
|
|
940
|
+
<?php
|
|
941
|
+
$client = get_post_meta(get_the_ID(), '_portfolio_client', true);
|
|
942
|
+
$url = get_post_meta(get_the_ID(), '_portfolio_url', true);
|
|
943
|
+
$date = get_post_meta(get_the_ID(), '_portfolio_date', true);
|
|
944
|
+
$technologies = get_post_meta(get_the_ID(), '_portfolio_technologies', true);
|
|
945
|
+
?>
|
|
946
|
+
|
|
947
|
+
<?php if ($client): ?>
|
|
948
|
+
<div class="portfolio-detail">
|
|
949
|
+
<strong><?php _e('Client:', 'portfolio-manager'); ?></strong>
|
|
950
|
+
<?php echo esc_html($client); ?>
|
|
951
|
+
</div>
|
|
952
|
+
<?php endif; ?>
|
|
953
|
+
|
|
954
|
+
<?php if ($url): ?>
|
|
955
|
+
<div class="portfolio-detail">
|
|
956
|
+
<strong><?php _e('Project URL:', 'portfolio-manager'); ?></strong>
|
|
957
|
+
<a href="<?php echo esc_url($url); ?>" target="_blank" rel="noopener">
|
|
958
|
+
<?php echo esc_html($url); ?>
|
|
959
|
+
</a>
|
|
960
|
+
</div>
|
|
961
|
+
<?php endif; ?>
|
|
962
|
+
|
|
963
|
+
<?php if ($date): ?>
|
|
964
|
+
<div class="portfolio-detail">
|
|
965
|
+
<strong><?php _e('Completion Date:', 'portfolio-manager'); ?></strong>
|
|
966
|
+
<?php echo esc_html(date_i18n(get_option('date_format'), strtotime($date))); ?>
|
|
967
|
+
</div>
|
|
968
|
+
<?php endif; ?>
|
|
969
|
+
|
|
970
|
+
<?php if ($technologies): ?>
|
|
971
|
+
<div class="portfolio-detail">
|
|
972
|
+
<strong><?php _e('Technologies:', 'portfolio-manager'); ?></strong>
|
|
973
|
+
<?php echo esc_html($technologies); ?>
|
|
974
|
+
</div>
|
|
975
|
+
<?php endif; ?>
|
|
976
|
+
</div>
|
|
977
|
+
|
|
978
|
+
<div class="entry-content">
|
|
979
|
+
<?php the_content(); ?>
|
|
980
|
+
</div>
|
|
981
|
+
|
|
982
|
+
<?php
|
|
983
|
+
$gallery = get_post_meta(get_the_ID(), '_portfolio_gallery', true);
|
|
984
|
+
if ($gallery):
|
|
985
|
+
$gallery_ids = explode(',', $gallery);
|
|
986
|
+
?>
|
|
987
|
+
<div class="portfolio-gallery">
|
|
988
|
+
<h2><?php _e('Project Gallery', 'portfolio-manager'); ?></h2>
|
|
989
|
+
<div class="gallery-grid">
|
|
990
|
+
<?php foreach ($gallery_ids as $image_id): ?>
|
|
991
|
+
<?php $image = wp_get_attachment_image_src($image_id, 'large'); ?>
|
|
992
|
+
<a href="<?php echo esc_url($image[0]); ?>" class="gallery-item">
|
|
993
|
+
<?php echo wp_get_attachment_image($image_id, 'medium'); ?>
|
|
994
|
+
</a>
|
|
995
|
+
<?php endforeach; ?>
|
|
996
|
+
</div>
|
|
997
|
+
</div>
|
|
998
|
+
<?php endif; ?>
|
|
999
|
+
|
|
1000
|
+
<footer class="entry-footer">
|
|
1001
|
+
<?php
|
|
1002
|
+
// Previous/Next navigation
|
|
1003
|
+
$prev_post = get_previous_post();
|
|
1004
|
+
$next_post = get_next_post();
|
|
1005
|
+
|
|
1006
|
+
if ($prev_post || $next_post):
|
|
1007
|
+
?>
|
|
1008
|
+
<nav class="portfolio-navigation">
|
|
1009
|
+
<?php if ($prev_post): ?>
|
|
1010
|
+
<div class="nav-previous">
|
|
1011
|
+
<a href="<?php echo esc_url(get_permalink($prev_post)); ?>">
|
|
1012
|
+
« <?php echo esc_html($prev_post->post_title); ?>
|
|
1013
|
+
</a>
|
|
1014
|
+
</div>
|
|
1015
|
+
<?php endif; ?>
|
|
1016
|
+
|
|
1017
|
+
<?php if ($next_post): ?>
|
|
1018
|
+
<div class="nav-next">
|
|
1019
|
+
<a href="<?php echo esc_url(get_permalink($next_post)); ?>">
|
|
1020
|
+
<?php echo esc_html($next_post->post_title); ?> »
|
|
1021
|
+
</a>
|
|
1022
|
+
</div>
|
|
1023
|
+
<?php endif; ?>
|
|
1024
|
+
</nav>
|
|
1025
|
+
<?php endif; ?>
|
|
1026
|
+
</footer>
|
|
1027
|
+
</article>
|
|
1028
|
+
<?php endwhile; ?>
|
|
1029
|
+
</div>
|
|
1030
|
+
|
|
1031
|
+
<?php
|
|
1032
|
+
get_footer();
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
### File: `public/templates/content-portfolio.php`
|
|
1036
|
+
|
|
1037
|
+
```php
|
|
1038
|
+
<?php
|
|
1039
|
+
/**
|
|
1040
|
+
* Content template for portfolio items
|
|
1041
|
+
*
|
|
1042
|
+
* @package Portfolio_Manager
|
|
1043
|
+
*/
|
|
1044
|
+
|
|
1045
|
+
$categories = get_the_terms(get_the_ID(), 'portfolio_category');
|
|
1046
|
+
$cat_classes = array();
|
|
1047
|
+
if ($categories && !is_wp_error($categories)) {
|
|
1048
|
+
foreach ($categories as $cat) {
|
|
1049
|
+
$cat_classes[] = 'cat-' . $cat->slug;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
?>
|
|
1053
|
+
|
|
1054
|
+
<div class="portfolio-item <?php echo esc_attr(implode(' ', $cat_classes)); ?>">
|
|
1055
|
+
<?php if (has_post_thumbnail()): ?>
|
|
1056
|
+
<div class="portfolio-thumbnail">
|
|
1057
|
+
<a href="<?php the_permalink(); ?>">
|
|
1058
|
+
<?php the_post_thumbnail('medium'); ?>
|
|
1059
|
+
</a>
|
|
1060
|
+
<div class="portfolio-overlay">
|
|
1061
|
+
<a href="<?php the_permalink(); ?>" class="portfolio-view">
|
|
1062
|
+
<?php _e('View Project', 'portfolio-manager'); ?>
|
|
1063
|
+
</a>
|
|
1064
|
+
</div>
|
|
1065
|
+
</div>
|
|
1066
|
+
<?php endif; ?>
|
|
1067
|
+
|
|
1068
|
+
<div class="portfolio-content">
|
|
1069
|
+
<h3 class="portfolio-title">
|
|
1070
|
+
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
|
|
1071
|
+
</h3>
|
|
1072
|
+
|
|
1073
|
+
<?php if ($categories && !is_wp_error($categories)): ?>
|
|
1074
|
+
<div class="portfolio-categories">
|
|
1075
|
+
<?php foreach ($categories as $category): ?>
|
|
1076
|
+
<a href="<?php echo esc_url(get_term_link($category)); ?>" class="portfolio-category">
|
|
1077
|
+
<?php echo esc_html($category->name); ?>
|
|
1078
|
+
</a>
|
|
1079
|
+
<?php endforeach; ?>
|
|
1080
|
+
</div>
|
|
1081
|
+
<?php endif; ?>
|
|
1082
|
+
|
|
1083
|
+
<div class="portfolio-excerpt">
|
|
1084
|
+
<?php the_excerpt(); ?>
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
</div>
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1092
|
+
## Assets
|
|
1093
|
+
|
|
1094
|
+
### File: `public/css/portfolio.css`
|
|
1095
|
+
|
|
1096
|
+
```css
|
|
1097
|
+
/* Portfolio Grid */
|
|
1098
|
+
.portfolio-grid {
|
|
1099
|
+
display: grid;
|
|
1100
|
+
gap: 30px;
|
|
1101
|
+
margin: 30px 0;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
.portfolio-grid.portfolio-col-2 {
|
|
1105
|
+
grid-template-columns: repeat(2, 1fr);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.portfolio-grid.portfolio-col-3 {
|
|
1109
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.portfolio-grid.portfolio-col-4 {
|
|
1113
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
@media (max-width: 768px) {
|
|
1117
|
+
.portfolio-grid {
|
|
1118
|
+
grid-template-columns: 1fr !important;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/* Portfolio Item */
|
|
1123
|
+
.portfolio-item {
|
|
1124
|
+
background: #fff;
|
|
1125
|
+
border: 1px solid #e0e0e0;
|
|
1126
|
+
border-radius: 8px;
|
|
1127
|
+
overflow: hidden;
|
|
1128
|
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.portfolio-item:hover {
|
|
1132
|
+
transform: translateY(-5px);
|
|
1133
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.portfolio-thumbnail {
|
|
1137
|
+
position: relative;
|
|
1138
|
+
overflow: hidden;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.portfolio-thumbnail img {
|
|
1142
|
+
width: 100%;
|
|
1143
|
+
height: auto;
|
|
1144
|
+
display: block;
|
|
1145
|
+
transition: transform 0.3s ease;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
.portfolio-item:hover .portfolio-thumbnail img {
|
|
1149
|
+
transform: scale(1.05);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.portfolio-overlay {
|
|
1153
|
+
position: absolute;
|
|
1154
|
+
top: 0;
|
|
1155
|
+
left: 0;
|
|
1156
|
+
right: 0;
|
|
1157
|
+
bottom: 0;
|
|
1158
|
+
background: rgba(0, 0, 0, 0.7);
|
|
1159
|
+
display: flex;
|
|
1160
|
+
align-items: center;
|
|
1161
|
+
justify-content: center;
|
|
1162
|
+
opacity: 0;
|
|
1163
|
+
transition: opacity 0.3s ease;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.portfolio-item:hover .portfolio-overlay {
|
|
1167
|
+
opacity: 1;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.portfolio-view {
|
|
1171
|
+
color: #fff;
|
|
1172
|
+
text-decoration: none;
|
|
1173
|
+
padding: 10px 20px;
|
|
1174
|
+
border: 2px solid #fff;
|
|
1175
|
+
border-radius: 4px;
|
|
1176
|
+
transition: background 0.3s ease;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.portfolio-view:hover {
|
|
1180
|
+
background: #fff;
|
|
1181
|
+
color: #000;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.portfolio-content {
|
|
1185
|
+
padding: 20px;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.portfolio-title {
|
|
1189
|
+
margin: 0 0 10px;
|
|
1190
|
+
font-size: 20px;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
.portfolio-title a {
|
|
1194
|
+
color: #333;
|
|
1195
|
+
text-decoration: none;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
.portfolio-title a:hover {
|
|
1199
|
+
color: #0073aa;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.portfolio-categories {
|
|
1203
|
+
margin: 10px 0;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.portfolio-category {
|
|
1207
|
+
display: inline-block;
|
|
1208
|
+
padding: 4px 12px;
|
|
1209
|
+
background: #f0f0f0;
|
|
1210
|
+
color: #666;
|
|
1211
|
+
font-size: 12px;
|
|
1212
|
+
border-radius: 3px;
|
|
1213
|
+
margin-right: 5px;
|
|
1214
|
+
text-decoration: none;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.portfolio-category:hover {
|
|
1218
|
+
background: #0073aa;
|
|
1219
|
+
color: #fff;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/* Portfolio Filters */
|
|
1223
|
+
.portfolio-filters {
|
|
1224
|
+
text-align: center;
|
|
1225
|
+
margin: 30px 0;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.portfolio-filter {
|
|
1229
|
+
padding: 10px 20px;
|
|
1230
|
+
margin: 0 5px;
|
|
1231
|
+
background: #f0f0f0;
|
|
1232
|
+
border: none;
|
|
1233
|
+
border-radius: 4px;
|
|
1234
|
+
cursor: pointer;
|
|
1235
|
+
transition: background 0.3s ease;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.portfolio-filter:hover,
|
|
1239
|
+
.portfolio-filter.active {
|
|
1240
|
+
background: #0073aa;
|
|
1241
|
+
color: #fff;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/* Single Portfolio */
|
|
1245
|
+
.portfolio-single {
|
|
1246
|
+
max-width: 1200px;
|
|
1247
|
+
margin: 0 auto;
|
|
1248
|
+
padding: 40px 20px;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.portfolio-featured-image {
|
|
1252
|
+
margin: 30px 0;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.portfolio-featured-image img {
|
|
1256
|
+
width: 100%;
|
|
1257
|
+
height: auto;
|
|
1258
|
+
border-radius: 8px;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.portfolio-details {
|
|
1262
|
+
background: #f9f9f9;
|
|
1263
|
+
padding: 20px;
|
|
1264
|
+
border-radius: 8px;
|
|
1265
|
+
margin: 30px 0;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.portfolio-detail {
|
|
1269
|
+
margin: 10px 0;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.portfolio-detail strong {
|
|
1273
|
+
display: inline-block;
|
|
1274
|
+
min-width: 150px;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.portfolio-gallery {
|
|
1278
|
+
margin: 40px 0;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.gallery-grid {
|
|
1282
|
+
display: grid;
|
|
1283
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1284
|
+
gap: 20px;
|
|
1285
|
+
margin: 20px 0;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
@media (max-width: 768px) {
|
|
1289
|
+
.gallery-grid {
|
|
1290
|
+
grid-template-columns: 1fr;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
.gallery-item img {
|
|
1295
|
+
width: 100%;
|
|
1296
|
+
height: auto;
|
|
1297
|
+
border-radius: 4px;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.portfolio-navigation {
|
|
1301
|
+
display: flex;
|
|
1302
|
+
justify-content: space-between;
|
|
1303
|
+
margin: 40px 0;
|
|
1304
|
+
padding: 20px 0;
|
|
1305
|
+
border-top: 1px solid #e0e0e0;
|
|
1306
|
+
}
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
### File: `public/js/portfolio.js`
|
|
1310
|
+
|
|
1311
|
+
```javascript
|
|
1312
|
+
(function($) {
|
|
1313
|
+
'use strict';
|
|
1314
|
+
|
|
1315
|
+
$(document).ready(function() {
|
|
1316
|
+
// Portfolio filtering
|
|
1317
|
+
$('.portfolio-filter').on('click', function() {
|
|
1318
|
+
var filter = $(this).data('filter');
|
|
1319
|
+
|
|
1320
|
+
// Update active state
|
|
1321
|
+
$('.portfolio-filter').removeClass('active');
|
|
1322
|
+
$(this).addClass('active');
|
|
1323
|
+
|
|
1324
|
+
// Filter items
|
|
1325
|
+
if (filter === '*') {
|
|
1326
|
+
$('.portfolio-item').fadeIn(300);
|
|
1327
|
+
} else {
|
|
1328
|
+
$('.portfolio-item').hide();
|
|
1329
|
+
$(filter).fadeIn(300);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// Lightbox for gallery (if using a lightbox plugin)
|
|
1334
|
+
if (typeof $.fn.magnificPopup !== 'undefined') {
|
|
1335
|
+
$('.gallery-item').magnificPopup({
|
|
1336
|
+
type: 'image',
|
|
1337
|
+
gallery: {
|
|
1338
|
+
enabled: true
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
})(jQuery);
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
### File: `admin/css/admin.css`
|
|
1348
|
+
|
|
1349
|
+
```css
|
|
1350
|
+
/* Portfolio Gallery Meta Box */
|
|
1351
|
+
.portfolio-gallery-container {
|
|
1352
|
+
padding: 10px 0;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
.portfolio-gallery-images {
|
|
1356
|
+
display: grid;
|
|
1357
|
+
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
1358
|
+
gap: 10px;
|
|
1359
|
+
margin-bottom: 15px;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.portfolio-gallery-image {
|
|
1363
|
+
position: relative;
|
|
1364
|
+
border: 1px solid #ddd;
|
|
1365
|
+
border-radius: 4px;
|
|
1366
|
+
overflow: hidden;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
.portfolio-gallery-image img {
|
|
1370
|
+
width: 100%;
|
|
1371
|
+
height: 100px;
|
|
1372
|
+
object-fit: cover;
|
|
1373
|
+
display: block;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
.portfolio-gallery-image .remove-image {
|
|
1377
|
+
position: absolute;
|
|
1378
|
+
top: 5px;
|
|
1379
|
+
right: 5px;
|
|
1380
|
+
background: #dc3232;
|
|
1381
|
+
color: #fff;
|
|
1382
|
+
border: none;
|
|
1383
|
+
border-radius: 50%;
|
|
1384
|
+
width: 24px;
|
|
1385
|
+
height: 24px;
|
|
1386
|
+
cursor: pointer;
|
|
1387
|
+
font-size: 16px;
|
|
1388
|
+
line-height: 1;
|
|
1389
|
+
display: flex;
|
|
1390
|
+
align-items: center;
|
|
1391
|
+
justify-content: center;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
.portfolio-gallery-image .remove-image:hover {
|
|
1395
|
+
background: #a00;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.portfolio-add-images {
|
|
1399
|
+
margin-top: 10px;
|
|
1400
|
+
}
|
|
1401
|
+
```
|
|
1402
|
+
|
|
1403
|
+
### File: `admin/js/admin.js`
|
|
1404
|
+
|
|
1405
|
+
```javascript
|
|
1406
|
+
(function($) {
|
|
1407
|
+
'use strict';
|
|
1408
|
+
|
|
1409
|
+
$(document).ready(function() {
|
|
1410
|
+
// Admin-specific JavaScript
|
|
1411
|
+
// Meta box interactions are handled inline in the meta box render function
|
|
1412
|
+
|
|
1413
|
+
console.log('Portfolio Manager admin scripts loaded');
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
})(jQuery);
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
---
|
|
1420
|
+
|
|
1421
|
+
## Uninstall
|
|
1422
|
+
|
|
1423
|
+
### File: `uninstall.php`
|
|
1424
|
+
|
|
1425
|
+
```php
|
|
1426
|
+
<?php
|
|
1427
|
+
/**
|
|
1428
|
+
* Uninstall script
|
|
1429
|
+
*
|
|
1430
|
+
* @package Portfolio_Manager
|
|
1431
|
+
*/
|
|
1432
|
+
|
|
1433
|
+
// Exit if accessed directly or not uninstalling
|
|
1434
|
+
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
|
1435
|
+
exit;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// Delete all portfolio posts
|
|
1439
|
+
$portfolio_posts = get_posts(array(
|
|
1440
|
+
'post_type' => 'portfolio',
|
|
1441
|
+
'posts_per_page' => -1,
|
|
1442
|
+
'post_status' => 'any',
|
|
1443
|
+
));
|
|
1444
|
+
|
|
1445
|
+
foreach ($portfolio_posts as $post) {
|
|
1446
|
+
// Delete post meta
|
|
1447
|
+
$meta_keys = array(
|
|
1448
|
+
'_portfolio_client',
|
|
1449
|
+
'_portfolio_url',
|
|
1450
|
+
'_portfolio_date',
|
|
1451
|
+
'_portfolio_technologies',
|
|
1452
|
+
'_portfolio_gallery',
|
|
1453
|
+
);
|
|
1454
|
+
|
|
1455
|
+
foreach ($meta_keys as $meta_key) {
|
|
1456
|
+
delete_post_meta($post->ID, $meta_key);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Delete post
|
|
1460
|
+
wp_delete_post($post->ID, true);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// Delete all portfolio categories
|
|
1464
|
+
$terms = get_terms(array(
|
|
1465
|
+
'taxonomy' => 'portfolio_category',
|
|
1466
|
+
'hide_empty' => false,
|
|
1467
|
+
));
|
|
1468
|
+
|
|
1469
|
+
foreach ($terms as $term) {
|
|
1470
|
+
wp_delete_term($term->term_id, 'portfolio_category');
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Delete options
|
|
1474
|
+
delete_option('pm_archive_columns');
|
|
1475
|
+
delete_option('pm_items_per_page');
|
|
1476
|
+
|
|
1477
|
+
// Clear any cached data
|
|
1478
|
+
wp_cache_flush();
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
---
|
|
1482
|
+
|
|
1483
|
+
## Usage Examples
|
|
1484
|
+
|
|
1485
|
+
### Basic Usage
|
|
1486
|
+
|
|
1487
|
+
**1. Create Portfolio Items**
|
|
1488
|
+
|
|
1489
|
+
Navigate to **Portfolio > Add New** in the WordPress admin and create portfolio items with:
|
|
1490
|
+
- Title and description
|
|
1491
|
+
- Featured image
|
|
1492
|
+
- Client name, project URL, completion date, technologies
|
|
1493
|
+
- Portfolio gallery images
|
|
1494
|
+
- Portfolio categories
|
|
1495
|
+
|
|
1496
|
+
**2. Display Portfolio on a Page**
|
|
1497
|
+
|
|
1498
|
+
Use shortcodes in any page or post:
|
|
1499
|
+
|
|
1500
|
+
```
|
|
1501
|
+
[portfolio limit="6" category="web-design" columns="3"]
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
**3. Display Filterable Portfolio Grid**
|
|
1505
|
+
|
|
1506
|
+
```
|
|
1507
|
+
[portfolio_grid columns="3"]
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
### Advanced Usage
|
|
1511
|
+
|
|
1512
|
+
**Custom Query in Theme**
|
|
1513
|
+
|
|
1514
|
+
```php
|
|
1515
|
+
<?php
|
|
1516
|
+
$args = array(
|
|
1517
|
+
'post_type' => 'portfolio',
|
|
1518
|
+
'posts_per_page' => 9,
|
|
1519
|
+
'tax_query' => array(
|
|
1520
|
+
array(
|
|
1521
|
+
'taxonomy' => 'portfolio_category',
|
|
1522
|
+
'field' => 'slug',
|
|
1523
|
+
'terms' => 'web-design',
|
|
1524
|
+
),
|
|
1525
|
+
),
|
|
1526
|
+
);
|
|
1527
|
+
|
|
1528
|
+
$portfolio_query = new WP_Query($args);
|
|
1529
|
+
|
|
1530
|
+
if ($portfolio_query->have_posts()):
|
|
1531
|
+
while ($portfolio_query->have_posts()): $portfolio_query->the_post();
|
|
1532
|
+
// Display portfolio item
|
|
1533
|
+
the_title();
|
|
1534
|
+
the_post_thumbnail();
|
|
1535
|
+
the_excerpt();
|
|
1536
|
+
endwhile;
|
|
1537
|
+
wp_reset_postdata();
|
|
1538
|
+
endif;
|
|
1539
|
+
?>
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
**Get Portfolio Meta Data**
|
|
1543
|
+
|
|
1544
|
+
```php
|
|
1545
|
+
<?php
|
|
1546
|
+
$client = get_post_meta(get_the_ID(), '_portfolio_client', true);
|
|
1547
|
+
$url = get_post_meta(get_the_ID(), '_portfolio_url', true);
|
|
1548
|
+
$date = get_post_meta(get_the_ID(), '_portfolio_date', true);
|
|
1549
|
+
$technologies = get_post_meta(get_the_ID(), '_portfolio_technologies', true);
|
|
1550
|
+
$gallery = get_post_meta(get_the_ID(), '_portfolio_gallery', true);
|
|
1551
|
+
|
|
1552
|
+
if ($gallery) {
|
|
1553
|
+
$gallery_ids = explode(',', $gallery);
|
|
1554
|
+
foreach ($gallery_ids as $image_id) {
|
|
1555
|
+
echo wp_get_attachment_image($image_id, 'large');
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
?>
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
---
|
|
1562
|
+
|
|
1563
|
+
## Key Features
|
|
1564
|
+
|
|
1565
|
+
### 1. Custom Post Type
|
|
1566
|
+
- **Portfolio** post type with full Gutenberg support
|
|
1567
|
+
- REST API enabled for headless WordPress
|
|
1568
|
+
- Custom menu icon and position
|
|
1569
|
+
- Archive and single post support
|
|
1570
|
+
|
|
1571
|
+
### 2. Custom Taxonomy
|
|
1572
|
+
- **Portfolio Categories** for organizing projects
|
|
1573
|
+
- Hierarchical structure (like categories)
|
|
1574
|
+
- REST API enabled
|
|
1575
|
+
- Admin column display
|
|
1576
|
+
|
|
1577
|
+
### 3. Meta Boxes
|
|
1578
|
+
- **Portfolio Details**: Client, URL, date, technologies
|
|
1579
|
+
- **Portfolio Gallery**: Multiple image upload with drag-and-drop
|
|
1580
|
+
- Nonce verification for security
|
|
1581
|
+
- Proper sanitization and validation
|
|
1582
|
+
|
|
1583
|
+
### 4. Templates
|
|
1584
|
+
- **Archive template**: Grid layout with filtering
|
|
1585
|
+
- **Single template**: Detailed project view with gallery
|
|
1586
|
+
- **Content template**: Reusable portfolio item display
|
|
1587
|
+
- Theme-overridable templates
|
|
1588
|
+
|
|
1589
|
+
### 5. Shortcodes
|
|
1590
|
+
- `[portfolio]`: Basic portfolio grid with attributes
|
|
1591
|
+
- `[portfolio_grid]`: Filterable portfolio grid
|
|
1592
|
+
- Customizable columns, limits, categories
|
|
1593
|
+
|
|
1594
|
+
### 6. Frontend Assets
|
|
1595
|
+
- Responsive grid layout
|
|
1596
|
+
- Hover effects and transitions
|
|
1597
|
+
- Filter functionality
|
|
1598
|
+
- Mobile-friendly design
|
|
1599
|
+
|
|
1600
|
+
---
|
|
1601
|
+
|
|
1602
|
+
## Best Practices Demonstrated
|
|
1603
|
+
|
|
1604
|
+
### Security
|
|
1605
|
+
✅ Nonce verification in meta boxes
|
|
1606
|
+
✅ Capability checks (`current_user_can`)
|
|
1607
|
+
✅ Input sanitization (`sanitize_text_field`, `esc_url_raw`)
|
|
1608
|
+
✅ Output escaping (`esc_html`, `esc_url`, `esc_attr`)
|
|
1609
|
+
✅ Prepared statements (not needed for this example)
|
|
1610
|
+
|
|
1611
|
+
### Performance
|
|
1612
|
+
✅ Conditional asset loading (only on portfolio pages)
|
|
1613
|
+
✅ Proper asset versioning
|
|
1614
|
+
✅ Efficient queries with `WP_Query`
|
|
1615
|
+
✅ `wp_reset_postdata()` after custom queries
|
|
1616
|
+
|
|
1617
|
+
### WordPress Standards
|
|
1618
|
+
✅ Internationalization (`__()`, `_e()`)
|
|
1619
|
+
✅ REST API support (`show_in_rest`)
|
|
1620
|
+
✅ Proper hook usage (`init`, `add_meta_boxes`, `save_post_portfolio`)
|
|
1621
|
+
✅ Autoloader for class files
|
|
1622
|
+
✅ Proper file organization
|
|
1623
|
+
|
|
1624
|
+
### User Experience
|
|
1625
|
+
✅ Intuitive admin interface
|
|
1626
|
+
✅ Media uploader integration
|
|
1627
|
+
✅ Responsive design
|
|
1628
|
+
✅ Filtering functionality
|
|
1629
|
+
✅ Previous/Next navigation
|
|
1630
|
+
|
|
1631
|
+
---
|
|
1632
|
+
|
|
1633
|
+
## Customization
|
|
1634
|
+
|
|
1635
|
+
### Change Grid Columns
|
|
1636
|
+
|
|
1637
|
+
Modify the shortcode attribute:
|
|
1638
|
+
```
|
|
1639
|
+
[portfolio columns="4"]
|
|
1640
|
+
```
|
|
1641
|
+
|
|
1642
|
+
Or update the CSS:
|
|
1643
|
+
```css
|
|
1644
|
+
.portfolio-grid.portfolio-col-4 {
|
|
1645
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1646
|
+
}
|
|
1647
|
+
```
|
|
1648
|
+
|
|
1649
|
+
### Add Custom Meta Fields
|
|
1650
|
+
|
|
1651
|
+
1. Add field to meta box render function
|
|
1652
|
+
2. Add sanitization in save function
|
|
1653
|
+
3. Display in single template
|
|
1654
|
+
|
|
1655
|
+
### Override Templates
|
|
1656
|
+
|
|
1657
|
+
Copy template files to your theme:
|
|
1658
|
+
```
|
|
1659
|
+
your-theme/
|
|
1660
|
+
└── portfolio-manager/
|
|
1661
|
+
├── archive-portfolio.php
|
|
1662
|
+
├── single-portfolio.php
|
|
1663
|
+
└── content-portfolio.php
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
Update template include function to check theme first:
|
|
1667
|
+
```php
|
|
1668
|
+
$theme_template = get_stylesheet_directory() . '/portfolio-manager/archive-portfolio.php';
|
|
1669
|
+
if (file_exists($theme_template)) {
|
|
1670
|
+
return $theme_template;
|
|
1671
|
+
}
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1674
|
+
---
|
|
1675
|
+
|
|
1676
|
+
## Testing Checklist
|
|
1677
|
+
|
|
1678
|
+
- [ ] Create portfolio items with all meta fields
|
|
1679
|
+
- [ ] Upload gallery images
|
|
1680
|
+
- [ ] Assign portfolio categories
|
|
1681
|
+
- [ ] Test archive page display
|
|
1682
|
+
- [ ] Test single portfolio page
|
|
1683
|
+
- [ ] Test shortcodes on pages
|
|
1684
|
+
- [ ] Test filtering functionality
|
|
1685
|
+
- [ ] Test responsive design
|
|
1686
|
+
- [ ] Test uninstall cleanup
|
|
1687
|
+
- [ ] Verify REST API endpoints
|
|
1688
|
+
- [ ] Test with Gutenberg editor
|
|
1689
|
+
- [ ] Check for PHP/JavaScript errors
|
|
1690
|
+
|
|
1691
|
+
---
|
|
1692
|
+
|
|
1693
|
+
## Comparison with Other Patterns
|
|
1694
|
+
|
|
1695
|
+
| Feature | This Plugin | Simple Procedural | OOP Plugin | MVC Plugin |
|
|
1696
|
+
|---------|-------------|-------------------|------------|------------|
|
|
1697
|
+
| **Complexity** | Medium | Low | Medium | High |
|
|
1698
|
+
| **File Count** | 10-15 | 1-3 | 5-10 | 15-30 |
|
|
1699
|
+
| **Custom Post Type** | ✅ Full | ❌ | ✅ | ✅ |
|
|
1700
|
+
| **Meta Boxes** | ✅ Advanced | ❌ | ✅ Basic | ✅ Advanced |
|
|
1701
|
+
| **Templates** | ✅ Full | ❌ | ✅ Basic | ✅ Full |
|
|
1702
|
+
| **Shortcodes** | ✅ Multiple | ✅ Basic | ✅ | ✅ |
|
|
1703
|
+
| **Admin UI** | ✅ Custom | ❌ | ✅ | ✅ Advanced |
|
|
1704
|
+
| **Best For** | Portfolio/Projects | Simple features | Medium plugins | Large apps |
|
|
1705
|
+
|
|
1706
|
+
---
|
|
1707
|
+
|
|
1708
|
+
## Summary
|
|
1709
|
+
|
|
1710
|
+
This example demonstrates a **complete custom post type plugin** with:
|
|
1711
|
+
|
|
1712
|
+
- ✅ Custom post type registration with full WordPress integration
|
|
1713
|
+
- ✅ Custom taxonomy for categorization
|
|
1714
|
+
- ✅ Advanced meta boxes with media uploader
|
|
1715
|
+
- ✅ Custom templates (archive, single, content)
|
|
1716
|
+
- ✅ Multiple shortcodes with filtering
|
|
1717
|
+
- ✅ Responsive CSS and JavaScript
|
|
1718
|
+
- ✅ Proper security, sanitization, and escaping
|
|
1719
|
+
- ✅ Internationalization support
|
|
1720
|
+
- ✅ Clean uninstall process
|
|
1721
|
+
- ✅ REST API support
|
|
1722
|
+
- ✅ Gutenberg compatibility
|
|
1723
|
+
|
|
1724
|
+
**Perfect for**: Portfolio sites, project showcases, case studies, team members, testimonials, or any content type requiring custom fields and taxonomies.
|
|
1725
|
+
|
|
1726
|
+
**Character Count**: ~31,500 characters
|
|
1727
|
+
|