If you would like to learn about installing and configuring the plugin please read this first.
If you are looking to understand more about the bundled templates please read this.
Introduction
The OpenAsset WordPress plugin is designed to give you a head start when synchronizing image, project and employee data to your website in a performant way. With the help of your web developer, you will have full control over the integration of OA data into your website.
This plugin has been designed to leverage core WordPress features to make development intuitive. The Employees and Projects are set up as custom post types which means concepts such as the_post() apply and work as documented within the WordPress documentation.
Some default templates are included within the OpenAsset WordPress plugin files and can be used as a starting point to interacting with your data. It is strongly recommended that you do not edit the template files directly within the OpenAsset WordPress plugin folder as this will mean that updating or uninstalling the plugin will result in your changes being lost.
Refer to the ‘Getting Started’ section of this document for best practices when working with these provided theme files.
Getting Started
To get started changing aspects of your templates, add a folder named ‘openasset’ to your theme directory. The plugin will automatically detect and start using files that fit the naming scheme listed below. To use the provided templates as a starting point, copy the template files included in the ‘templates’ directory as a starting point, or you can create your own.
Theme Root folder
‘Openasset’ folder
archive-employee.php
single-employee.php
archive-project.php
single-project.php
As you build your templates, you may also want to make a ‘template-parts’ folder to keep your theme’s code more manageable.
Reading Developer Logs
With the ‘Enable Logging?’ setting on the ‘General Settings’ tab of the plugin turned on, the plugin will add the following event logs to the file ‘wp-content/debug.log’:
‘Rescheduling OA feed refresh frequency.’
This indicates the ‘General Settings’ tab of the plugin has been saved and the new value for ‘Auto Sync Frequency’ will be processed'Did not reschedule as the time frequency is the same.'
If the saved ‘Auto Sync Frequency’ value is the same as before no further action will be taken'Rescheduling OA feed refresh frequency completed. Auto syncing is turned off.'
If the user has updated the ‘Auto Sync Frequency’ and indicated they do not want the sync to occur automatically.'Rescheduling OA feed refresh frequency completed.'
Updating the ‘Auto Sync Frequency’ setting has completed.'Updating OpenAsset feed.'
Indicates the data sync for OpenAsset data started'Finished updating OpenAsset feed.'
Indicates when the data sync for OpenAsset data ends'Unable to create post for employee id: {{NUMBER}}'
When the creation of an employee post has failed.‘Image with external ID {{NUMBER}} already exists.’
If the plugin detects that an image has already been downloaded to the WordPress site.‘Failed to download image from:{{URL}}. Error: {{ERROR_MESSAGE}}’
When the plugin fails to download an image with the given error.'Adding sort value to posts.'
Indicates the plugin is starting to add metadata to employee and project posts‘Adding sort value to {{ENTITY}} posts exited before updating as sort by is not set.’
Indicates the user has not chosen a ‘Sort employees by’ or ‘Sort projects by’ option from the ‘Data Options’ tab of the plugin.'Adding sort value to posts completed.'
Indicates the plugin has finished adding metadata used for sorting to employee and project posts'Decryption error: Incorrect format.'
Indicates an error with processing your OpenAsset credentials, please contact support.
The plugin may also log additional errors relating to individual failed requests to the OpenAsset API.
Accessing Stored Plugin Settings
Plugin settings can be accessed anywhere using the standard WordPress get_option() function:
<?php
$oa_settings_options = get_option( 'openasset_settings' );
?>
This multi-dimensional array contains the following structure:
general-settings - array
you-real-api-token-id - string
The API Token ID from OpenAsset, used by the plugin to access OpenAsset data. It is recommended not to display this on your site.your-real-api-token - string
The API Token from OpenAsset, used by the plugin to access OpenAsset data. It is recommended not to display this on your site.client-instance-url - string
The OpenAsset url that the plugin will request data fromenable-logging - string ‘true’ | ‘false’
Indicates if plugin event logging has been enabled (see ‘Reading Developer Logs’feed-frequency - string ‘none’ | ‘openasset_30’ | ‘openasset_60’ | ‘openasset_8’ | ‘openasset_24’
The selected auto-update frequency for OpenAsset data.‘none’ - no auto updating
‘openasset_30’ - update every 30 minutes
‘openasset_60’ - update every 60 minutes
‘openasset_8’ - update every 8 hours
‘openasset_24 - update every 24 hours’
project-name-plural - string
The custom plural name for the project post typeproject-name-singular - string
The custom singular name for the project post typeproject-url-key - string
The custom url name to be used for the single and archive project pagesproject-show - string ‘yes’ | ‘no’
Indicates if the project post type should be shown on the websiteemployee-name-plural - string
The custom plural name for the employee post typeemployee-name-singular - string
The custom singular name for the employee post typeemployee-url-key - string
The custom url name to be used for the single and archive employee pagesemployee-show - string ‘yes’ | ‘no’
Indicates if the employee post type should be shown on the website
Data-options - array
employee-order-by - string ‘Asc’ | ‘Desc’
Indicates the intended ordering direction for employeesemployee-images-tagged-show-on-website - ‘yes’ | ‘no’
Indicates that only images that are tagged with a ‘show on website’ boolean field will be synced with an employee.maximum-images-per-employee - string
The number of images that will be shown for each employeeemployee-images-sort-by - string ‘created’ | ‘uploaded’ | ‘updated’ | ‘rank’
Indicates the OpenAsset field that dictates the order in which images attached to employee posts should be displayedemployee-images-order - string ‘Asc’ | ‘Desc’
Indicates the sort direction of the images attached to employee postsproject-order-by - string ‘Asc’ | ‘Desc’
Indicates the intended ordering direction for projectsproject-images-tagged-show-on-website - ‘yes’ | ‘no’
Indicates that only images that are tagged with a ‘show on website’ boolean field will be synced with a project.maximum-images-per-project - string
The number of images that will be shown for each projectproject-images-sort-by - string ‘created’ | ‘uploaded’ | ‘updated’ | ‘rank’
Indicates the OpenAsset field that dictates the order in which images attached to employee posts should be displayedproject-images-order-by - string ‘Asc’ | ‘Desc’
Indicates the sort direction of the images attached to project postsfile-options - array
Array of strings indicating which fields will be displayed on files‘description’
‘caption’
‘photographer_id’
‘copyright_holder_id’
employee-criteria-fields - array
Array of integer ids of OpenAsset fields that should be displayed on Employee postsproject-criteria-fields - array
Array of integer ids of OpenAsset fields that should be displayed on Project postsroles-criteria-fields - array
Array of integer ids of OpenAsset fields that should be displayed for the roles that Employees have on Projectscheck-credentials - boolean
Internal variable used for managing data sync behaviour
Project Archive Pages
The ‘project-archive.php’ file contains the code for the list view of all synced projects.
Looping on All Synced Projects
As with other custom post type archive pages, the_posts() will be populated with an array of WordPress post objects:
<?php
// arhive-project.php
while ( have_posts() ) :
the_post();
// get the id of the project
$project_id = get_the_ID();
// get the name of the project
$project_title = get_the_title();
// get the thumbnail URL for the project
$project_thumbnail = get_the_post_thumbnail_url();
// get the link for the project
$project_permalink = get_permalink();
// get all synced OpenAsset data for the project
$project_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
endwhile;
?>
Searching on Projects
Setting the ‘s’ URL parameter will be taken as a search term when on the project archive page:
<?php
// archive-project.php
// get the url of our project archive page
$project_post_type = get_post_type_object( 'project' );
$project_slug = $project_post_type ? $project_post_type->rewrite['slug'] : '';
?>
<form role="search" method="get" action="<?= "/$project_slug" ?>">
<input type="search" placeholder="Search..." name="s" />
<button type="submit">
Search
</button>
</form>
Project Keywords and Project Keyword Categories
In OpenAsset, a project keyword category can have multiple project keywords. The OpenAsset plugin offers settings to configure which project keyword categories and their (associated project keywords) are stored on the site. To utilize standard WordPress functionality, project keywords are stored as ‘terms’ with their project keyword category data stored as ‘term meta’. The following code can be used to access the stored keyword categories and their associated keywords:
<?php
// get the selected plugin settings
$oa_settings_options = get_option( 'openasset_settings' );
// array of ids of selected keyword categories in the plugin UI
$project_criteria_keyword_categories = $oa_settings_options['data-options']['project-criteria-keyword-categories'] ?: array();
// get the keyword categories marked as selected in the plugin UI
$keyword_categories = get_terms(
array(
'taxonomy' => 'keyword',
'hide_empty' => true,
'parent' => 0,
'meta_query' => array(
array(
'key' => 'openasset_id',
'compare' => 'IN',
'value' => $project_criteria_keyword_categories,
),
),
)
);
// keyword categories as array of WordPress 'term' objects
$keyword_categories;
foreach ( $keyword_categories as $keyword_category ) :
// get keywords for specific keyword category id
$keywords = get_terms(
array(
'taxonomy' => 'keyword',
'hide_empty' => true,
'parent' => $keyword_category_term_id,
)
);
// keyword is a WordPress term object
foreach( $keywords as $keyword ) :
// the keyword name
$keyword_name = $keyword->name;
endforeach;
endforeach;
?>
Filtering the projects list by Project Keywords
To create a set of project filters based off of project keywords, the following can be used:
<?php
// archive-project.php
// create an identifier for our filter action
$nonce = wp_create_nonce( 'keyword_filters_nonce' );
// get the url of our project archive page
$project_post_type = get_post_type_object( 'project' );
$project_slug = $project_post_type
? $project_post_type->rewrite['slug']
: '';
// get the selected plugin settings
$oa_settings_options = get_option( 'openasset_settings' );
// array of ids of selected keyword categories in the plugin UI
$project_criteria_keyword_categories = $oa_settings_options['data-options']['project-criteria-keyword-categories'] ?: array();
// get the keyword categories marked as selected in the plugin UI
$keyword_categories = get_terms(
array(
'taxonomy' => 'keyword',
'hide_empty' => true,
'parent' => 0,
'meta_query' => array(
array(
'key' => 'openasset_id',
'compare' => 'IN',
'value' => $project_criteria_keyword_categories,
),
),
)
);
?>
<form action="<?= "/$project_slug" ?>">
<ul>
<?php
foreach( $keyword_categories as $keyword_category ) :
$keyword_category_name = $keyword_category->name;
$keywords = get_terms(
array(
'taxonomy' => 'keyword',
'hide_empty' => true,
'parent' => $keyword_category->term_id,
)
);
?>
<?php foreach( $keywords as $keyword ) : ?>
<li>
<label>
<input type="checkbox" name="keywordfilters[]" value="<?= esc_attr($keyword->slug) ?>" />
<?= "$keyword_category->name - $keyword->name" ?>
</label>
</li>
<?php
endforeach;
?>
<?php endforeach; ?>
</ul>
<input type="hidden" name="keyword_filters_nonce" value="<?= $nonce ?>">
<button class="oa-applyfilters" type="submit">
Apply
</button>
</form>
<?php
if ( have_posts() ) :
// have_posts() will contain the array of filtered projects
while ( have_posts() ) :
the_post();
endwhile;
endif;
?>
Project Single Pages
Accessing OpenAsset Data for a Project
Access Project Field Data
The synced OpenAsset Project data can be accessed in the following way:
<?php
// single-project.php
$project_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
?>
Please see ‘The OpenAsset Project Post Metadata’ section for more information on the structure of this data.
Note - This will include all data synchronized for a Project and may also include field data that has not been chosen to be shown from the plugin ‘Data Options’ screen. The following can be used to access the project fields data and the project fields that have been chosen to be shown form the ‘Data Options’ screen:
<?php
// single-project.php
// get OpenAsset data options
$oa_data_options = get_option( 'openasset_data' );
// get OpenAsset plugin settings
$oa_settings_options = get_option( 'openasset_settings' );
// get array of all project fields
$project_fields = $oa_data_options[fields']['project'];
// get array of selected (visible) project field ids
$included_project_field_ids = $oa_settings_options['data-options']['project-criteria-fields'];
// we can detect which fields should be included
foreach( $project_fields as $project_field ) :
$field_included = in_array( $project_field['id'], $included_project_field_ids );
$field_value = null;
if ( $field_included ) :
if ( isset( $oa_project_data[$project_field['rest_code']] ) ) :
$field_value = $oa_project_data[$project_field['rest_code']];
endif;
foreach( $oa_project_data['fields'] as $project_data_field ) :
if ( $project_data_field['id'] === $project_field['id'] && isset( $project_data_field['values'] ) ) :
$field_value = $project_data_field['values']['0'];
break;
endif;
endforeach;
endif;
endforeach;
?>
Access Project Image Data
If the data option to download project images is turned on, the OpenAsset plugin will download images attached to projects and store them in the ‘wp-content/uploads’ folder.
The main (hero) image for each project post can be retrieved using standard WordPress functionality:
<?php
// single-project.php
$post_thumbnail_url = get_the_post_thumbnail_url();
?>
The following code can be used to retrieve all images for a project, respecting the settings chosen in the plugin control panel:
<?php
// single-project.php
// get OpenAsset plugin settings
$oa_settings_options = get_option( 'openasset_settings' );
// get the id of the project hero image we want to exclude from our results
$post_thumbnail_id = get_post_thumbnail_id();
// define our image search arguments
$args = array(
'post_type' => 'attachment',
'post_mime_type' => 'image',
'post_status' => 'inherit',
'posts_per_page' => $oa_settings_options['data-options']['maximum-images-per-project'],
'posts_parent' => get_the_ID(),
'exclude' => $post_thumbnail_id,
'meta_key' => $oa_settings_options['data-options']['project-images-sort-by'],
'orderby' => 'meta_value',
'order' => $oa_settings_options['data-options']['project-images-order-by'],
);
$project_images = get_posts( $args );
if ( $project_images ) :
foreach ( $project_images as $project_image ) :
// project image url
$image_url = wp_get_attachment_image_src( $project_image->ID, 'full' );
endforeach;
endif;
?>
Access Project Role Data
If the data option to synchronize project role data is switched on, the OpenAsset WordPress plugin will store the project employee role data and make it accessible via the following code:
<?php
// single-project.php
// get OpenAsset data options
$oa_data_options = get_option( 'openasset_data' );
// get all synced OpenAsset data for the project
$employee_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
// get all role fields
$role_fields = $oa_data_options['grid-columns'];
// get the project roles for the employee
$employee_role_data = $post_metadata['employees'];
// get the role fields selected from within the plugin
$included_role_fields = $oa_settings_options['data-options']['roles-criteria-fields'];
// get the array of employee ids from the roles
$role_employee_ids = array_map( function( $employee ) {
return $employee['id'];
}, $employee_role_data );
// define our project search arguments
$args = array(
'post_type' => 'employee',
'meta_key' => 'openasset_id',
'meta_value' => $role_employee_ids,
'posts_per_page' => -1,
);
$project_employees = get_posts( $args );
// loop through each role
foreach ( $employee_role_data as $employee_role_datum ) :
// find the post object for the project
$role_employee = null;
foreach( $project_employees as $project_employee ) :
if ( $project_employee->id == $employee_role_datum['id'] ) :
$role_employee = $project_employee;
break;
endif;
endforeach;
if ( !$role_employee ) break;
// loop through each employee role
foreach ( $employee_role_datum['roles']['rows'] as $role_row ) :
// loop through each role column field
foreach ( $role_fields as $role_field ) :
// detect if we should show the field
$field_included = in_array( $role_field['id'], $included_role_fields );
if ( isset ( $role_row[$role_field['code'] ) ) :
$field_value = $role_row[$role_field['code'];
endif;
endforeach;
endforeach;
endforeach;
?>
Employee Archive Pages
The ‘employee-archive.php’ file contains the code for the list view of all synced employees.
<?php
// archive-employee.php
// get the url of our employee archive page
$employee_post_type = get_post_type_object( 'employee' );
$employee_slug = $employee_post_type ? $employee_post_type->rewrite['slug'] : '';
?>
<form role="search" method="get" action="<?= "/$employee_slug" ?>">
<input type="search" placeholder="Search..." name="s" />
<button type="submit">
Search
</button>
</form>
Looping On All Synced Employees
As with other WordPress custom post type archive pages, the_posts() will be populated with an array of WordPress post objects:
<?php
// archive-employee.php
while ( have_posts() ) :
the_post();
// get the id of the employee
$employee_id = get_the_ID();
// get the name of the employee
$employee_title = get_the_title();
// get the thumbnail URL for the employee
$employee_thumbnail = get_the_post_thumbnail_url();
// get the link for the employee
$employee_permalink = get_permalink();
// get all synced OpenAsset data for the employee
$employee_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
endwhile;
?>
Searching on Employees
Setting the ‘s’ URL parameter will be taken as a search term when on the employee archive page:
<?php
// archive-employee.php
// get the url of our employee archive page
$employee_post_type = get_post_type_object( 'employee' );
$employee_slug = $employee_post_type ? $employee_post_type->rewrite['slug'] : '';
?>
<form role="search" method="get" action="<?= "/$employee_slug" ?>">
<input type="search" placeholder="Search..." name="s" />
<button type="submit">
Search
</button>
</form>
Employee Single Pages
Accessing OpenAsset Data for an Employee
Access Employee Field Data
The synced OpenAsset Employee data can be accessed in the following way:
<?php
// single-employee.php
$employee_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
?>
Please see ‘The OpenAsset Employee Post Metadata’ section for more information of the structure of this data.
Note - This will include all data synchronized for an Employee and may also include field data that has not been chosen to be shown from the plugin ‘Data Options’ screen. The following can be used to access the employee fields data and the employee fields that have been chosen to be shown from the ‘Data Options’ screen:
<?php
// single-employee.php
// get OpenAsset data options
$oa_data_options = get_option( 'openasset_data' );
// get OpenAsset plugin settings
$oa_settings_options = get_option( 'openasset_settings' );
// get array of all employee fields
$employee_fields = $oa_data_options['fields']['employee'];
// get array of selected (visible) employee field ids
$included_employee_field_ids = $oa_settings_options['data-options']['employee-criteria-fields'];
// we can detect which fields should be included
foreach( $employee_fields as $employee_field ) :
$field_included = in_array( $employee_field['id'], $included_employee_field_ids );
if ( $field_included ) :
// and find if the field has a value set
if ( isset( $oa_employee_data[$employee_field['rest_code']] ) ) :
// The OpenAsset field value
$field_value = $oa_employee_data[$employee_field['rest_code']];
endif;
endif;
endforeach;
?>
Access Employee Image Data
If the data option to download employee images is turned on, the OpenAsset plugin will download images attached to employees and store them in the ‘wp-content/uploads’ folder.
The main (hero) image for each employee post can be retrieved using standard WordPress functionality:
<?php
// single-employee.php
$post_thumbnail_url = get_the_post_thumbnail_url();
?>
The following code can be used to retrieve all images for an employee, respecting the settings chosen in the plugin control panel:
<?php
// single-employee.php
// get OpenAsset plugin settings
$oa_settings_options = get_option( 'openasset_settings' );
// get the id of the employee hero image we want to exclude from our results
$post_thumbnail_id = get_post_thumbnail_id();
// define our image search arguments
$args = array(
'post_type' => 'attachment',
'post_mime_type' => 'image',
'post_status' => 'inherit',
'posts_per_page' => $oa_settings_options['data-options']['maximum-images-per-employee'],
'posts_parent' => get_the_ID(),
'exclude' => $post_thumbnail_id,
'meta_key' => $oa_settings_options['data-options']['employee-images-sort-by'],
'orderby' => 'meta_value',
'order' => $oa_settings_options['data-options']['employee-images-order-by'],
);
$employee_images = get_posts( $args );
if ( $employee_images ) :
foreach ( $employee_images as $employee_image ) :
// employee image url
$image_url = wp_get_attachment_image_src( $employee_image->ID, 'full' );
endforeach;
endif;
?>
Accessing Employee Role Data
If the data option to synchronize employee role data is switched on, the OpenAsset WordPress plugin will store the employee project role data and make it accessible via the following code:
<?php
// single-employee.php
// get OpenAsset data options
$oa_data_options = get_option( 'openasset_data' );
// get all synced OpenAsset data for the employee
$employee_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
// get all role fields
$role_fields = $oa_data_options['grid-columns'];
// get the project roles for the employee
$project_role_data = $post_metadata['projects'];
// get the role fields selected from within the plugin
$included_role_fields = $oa_settings_options['data-options']['roles-criteria-fields'];
// get the array of project ids from the roles
$role_project_ids = array_map( function( $project ) {
return $project['id'];
}, $project_role_data );
// define our project search arguments
$args = array(
'post_type' => 'project',
'meta_key' => 'openasset_id',
'meta_value' => $role_project_ids,
'posts_per_page' => -1,
);
$employee_projects = get_posts( $args );
// loop through each role
foreach ( $project_role_data as $project_role_datum ) :
// find the post object for the project
$role_project = null;
foreach( $employee_projects as $employee_project ) :
if ( $employee_project->id == $project_role_datum['id'] ) :
$role_project = $employee_project;
break;
endif;
endforeach;
if ( !$role_project ) break;
// loop through each project role
foreach ( $project_role_datum['roles']['rows'] as $role_row ) :
// loop through each role column field
foreach ( $role_fields as $role_field ) :
// detect if we should show the field
$field_included = in_array( $role_field['id'], $included_role_fields );
if ( isset ( $role_row[$role_field['code'] ) ) :
$field_value = $role_row[$role_field['code'];
endif;
endforeach;
endforeach;
endforeach;
?>
The OpenAsset Project Post Metadata
You can access all of the synced Project data by requesting the post metadata:
<?php
$project_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
?>
This multi-dimensional array has the following structure:
id - int
The ID of the project assigned by OpenAssetcode - string
The project code field data from OpenAssetname - string
The project name field data from OpenAssethero_image_id - int
The ID of the hero image assigned to the project within OpenAssetprojectKeywords - array
Array of arrays which contain an integer ‘id’ key with the value set to the id of a project keyword assigned to a project in OpenAssetemployees - array
Array of arrays containing information about the employees that have roles on this project. These contain the following structure:id - int
The id of employee who worked on the projectroles - array
rows - array
Array keyed with grid column rest codes and the value set to those fields in OpenAssetoffset - int
Query specific variable used for paginationlimit - int
Query specific variable used for pagination
fields - array
An array of arrays containing the data assigned to custom fields in OpenAsset. These fields will be unique to your OpenAsset system and will contain the following structure:id - int
The ID of the custom OpenAsset Fieldvalues - array
The first element of this array will contain the value set for the custom field in OpenAsset
This structure differs from the employee metadata in that custom field values are available on the ‘fields’ array.
The OpenAsset Employee Post Metadata
You can access all of the synced Employee data by requesting the post metadata:
<?php
$employee_openasset_data = get_post_meta(
get_the_ID(),
'openasset_data',
true
);
?>
This multi-dimensional array will be unique to your OpenAsset with system however by default you will have at least the following structure:
id - int
The ID assigned to the employee by OpenAssetcode - string
The employee code field data from OpenAssetfirst_name - string
The first name field data from OpenAssetlast_name - string
The last name field data from OpenAssethero_image_id - int
The ID of the hero image assigned to the employee within OpenAssetprojects - array
Array of arrays containing information about the projects in which this employee has a role. These contain the following structure:id - int
The id of project on which the employee has a roleroles - array
rows - array
Array keyed with grid column rest codes and the value set to those fields in OpenAssetoffset - int
Query specific variable used for paginationlimit - int
Query specific variable used for pagination
This structure differs from the project metadata in that custom fields values are available directly on the employee array.
Accessing Image Metadata
The OpenAsset WordPress plugin makes various file data available:
<?php
$images // array of WordPress attachment post objects
foreach( $images as $image ) :
// image caption
$caption = get_post_field( 'post_excerpt', $image->ID );
// image description
$description = get_post_field( 'post_content', $image->ID );
// photographer
$photographer = get_post_meta( $post_image->ID, 'photographer', true );
// copyright holder
$copyright_holder = get_post_meta( $post_image->ID, 'copyright_holder', true );
// created timestamp
$created_at = get_post_meta( $post_image->ID, 'created', true );
// updated timestamp
$updated_at = get_post_meta( $post_image->ID, 'updated', true );
// uploaded timestamp
$uploaded_at = get_post_meta( $post_image->ID, 'uploaded', true );
// image rank
$rank = get_post_meta( $post_image->ID, 'rank', true );
// project display order
$project_display_order = get_post_meta( $post_image->ID, 'project_display_order', true );
endforeach;
?>