Onboarding: Add create homepage logic to "Customize Appearance" step (https://github.com/woocommerce/woocommerce-admin/pull/2889)
This commit is contained in:
parent
5b0af85472
commit
f095466442
|
@ -37,7 +37,7 @@ class TaskDashboard extends Component {
|
|||
}
|
||||
|
||||
getTasks() {
|
||||
const { shippingZonesCount, tasks } = wcSettings.onboarding;
|
||||
const { customLogo, hasHomepage, hasProducts, shippingZonesCount } = wcSettings.onboarding;
|
||||
const { profileItems, query } = this.props;
|
||||
|
||||
return [
|
||||
|
@ -61,7 +61,7 @@ class TaskDashboard extends Component {
|
|||
'Add products manually, import from a sheet or migrate from another platform',
|
||||
'wooocommerce-admin'
|
||||
),
|
||||
before: tasks.products ? (
|
||||
before: hasProducts ? (
|
||||
<i className="material-icons-outlined">check_circle</i>
|
||||
) : (
|
||||
<i className="material-icons-outlined">add_box</i>
|
||||
|
@ -69,7 +69,7 @@ class TaskDashboard extends Component {
|
|||
after: <i className="material-icons-outlined">chevron_right</i>,
|
||||
onClick: () => updateQueryString( { task: 'products' } ),
|
||||
container: <Products />,
|
||||
className: tasks.products ? 'is-complete' : null,
|
||||
className: hasProducts ? 'is-complete' : null,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
|
@ -80,6 +80,7 @@ class TaskDashboard extends Component {
|
|||
after: <i className="material-icons-outlined">chevron_right</i>,
|
||||
onClick: () => updateQueryString( { task: 'appearance' } ),
|
||||
container: <Appearance />,
|
||||
className: customLogo && hasHomepage ? 'is-complete' : null,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@ import { getHistory, getNewPath } from '@woocommerce/navigation';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { WC_ADMIN_NAMESPACE } from 'wc-api/constants';
|
||||
import withSelect from 'wc-api/with-select';
|
||||
|
||||
class Appearance extends Component {
|
||||
|
@ -26,8 +27,8 @@ class Appearance extends Component {
|
|||
super( props );
|
||||
|
||||
this.stepVisibility = {
|
||||
homepage: ! wcSettings.onboarding.hasHomepage,
|
||||
import: ! wcSettings.onboarding.hasProducts,
|
||||
logo: ! wcSettings.onboarding.customLogo,
|
||||
};
|
||||
|
||||
this.state = {
|
||||
|
@ -38,6 +39,7 @@ class Appearance extends Component {
|
|||
};
|
||||
|
||||
this.completeStep = this.completeStep.bind( this );
|
||||
this.createHomepage = this.createHomepage.bind( this );
|
||||
this.importProducts = this.importProducts.bind( this );
|
||||
this.updateLogo = this.updateLogo.bind( this );
|
||||
this.updateNotice = this.updateNotice.bind( this );
|
||||
|
@ -69,6 +71,7 @@ class Appearance extends Component {
|
|||
if ( 'logo' === step && isRequestSuccessful ) {
|
||||
createNotice( 'success', __( 'Store logo updated sucessfully.', 'woocommerce-admin' ) );
|
||||
this.completeStep();
|
||||
wcSettings.onboarding.customLogo = themeMods.custom_logo ? true : false;
|
||||
}
|
||||
|
||||
if ( 'notice' === step && isRequestSuccessful ) {
|
||||
|
@ -95,7 +98,10 @@ class Appearance extends Component {
|
|||
const { createNotice } = this.props;
|
||||
this.setState( { isPending: true } );
|
||||
|
||||
apiFetch( { path: '/wc-admin/v1/onboarding/tasks/import_sample_products', method: 'POST' } )
|
||||
apiFetch( {
|
||||
path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/import_sample_products`,
|
||||
method: 'POST',
|
||||
} )
|
||||
.then( result => {
|
||||
if ( result.failed && result.failed.length ) {
|
||||
createNotice(
|
||||
|
@ -119,12 +125,32 @@ class Appearance extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
createHomepage() {
|
||||
const { createNotice } = this.props;
|
||||
this.setState( { isPending: true } );
|
||||
|
||||
apiFetch( { path: '/wc-admin/v1/onboarding/tasks/create_homepage', method: 'POST' } )
|
||||
.then( response => {
|
||||
createNotice( response.status, response.message );
|
||||
|
||||
this.setState( { isPending: false } );
|
||||
if ( response.edit_post_link ) {
|
||||
window.location = `${ response.edit_post_link }&wc_onboarding_active_task=homepage`;
|
||||
}
|
||||
} )
|
||||
.catch( error => {
|
||||
createNotice( 'error', error.message );
|
||||
this.setState( { isPending: false } );
|
||||
} );
|
||||
}
|
||||
|
||||
updateLogo() {
|
||||
const { options, themeMods, updateOptions } = this.props;
|
||||
const { logo } = this.state;
|
||||
const updateThemeMods = logo ? { ...themeMods, custom_logo: logo.id } : themeMods;
|
||||
|
||||
updateOptions( {
|
||||
[ `theme_mods_${ options.stylesheet }` ]: { ...themeMods, custom_logo: logo.id },
|
||||
[ `theme_mods_${ options.stylesheet }` ]: updateThemeMods,
|
||||
} );
|
||||
}
|
||||
|
||||
|
@ -171,13 +197,15 @@ class Appearance extends Component {
|
|||
),
|
||||
content: (
|
||||
<Fragment>
|
||||
<Button isPrimary>{ __( 'Create homepage', 'woocommerce-admin' ) }</Button>
|
||||
<Button isPrimary onClick={ this.createHomepage }>
|
||||
{ __( 'Create homepage', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
<Button onClick={ () => this.completeStep() }>
|
||||
{ __( 'Skip', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
</Fragment>
|
||||
),
|
||||
visible: true,
|
||||
visible: this.stepVisibility.homepage,
|
||||
},
|
||||
{
|
||||
key: 'logo',
|
||||
|
@ -194,7 +222,7 @@ class Appearance extends Component {
|
|||
</Button>
|
||||
</Fragment>
|
||||
),
|
||||
visible: this.stepVisibility.logo,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
key: 'notice',
|
||||
|
@ -244,7 +272,7 @@ class Appearance extends Component {
|
|||
|
||||
export default compose(
|
||||
withSelect( select => {
|
||||
const { getOptions, getOptionsError, isOptionsRequesting } = select( 'wc-api' );
|
||||
const { getOptions, getOptionsError, isUpdateOptionsRequesting } = select( 'wc-api' );
|
||||
|
||||
const options = getOptions( [
|
||||
'woocommerce_demo_store',
|
||||
|
@ -252,10 +280,7 @@ export default compose(
|
|||
'stylesheet',
|
||||
] );
|
||||
const themeModsName = `theme_mods_${ options.stylesheet }`;
|
||||
const themeOptions =
|
||||
options.stylesheet && ! wcSettings.onboarding.customLogo
|
||||
? getOptions( [ themeModsName ] )
|
||||
: null;
|
||||
const themeOptions = options.stylesheet ? getOptions( [ themeModsName ] ) : null;
|
||||
const themeMods =
|
||||
themeOptions && themeOptions[ themeModsName ] ? themeOptions[ themeModsName ] : {};
|
||||
|
||||
|
@ -273,9 +298,9 @@ export default compose(
|
|||
}
|
||||
const hasErrors = Boolean( errors.length );
|
||||
const isRequesting =
|
||||
Boolean( isOptionsRequesting( [ themeModsName ] ) ) ||
|
||||
Boolean( isUpdateOptionsRequesting( [ themeModsName ] ) ) ||
|
||||
Boolean(
|
||||
isOptionsRequesting( [ 'woocommerce_demo_store', 'woocommerce_demo_store_notice' ] )
|
||||
isUpdateOptionsRequesting( [ 'woocommerce_demo_store', 'woocommerce_demo_store_notice' ] )
|
||||
);
|
||||
|
||||
return { errors, getOptionsError, hasErrors, isRequesting, options, themeMods };
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { getResourceName } from '../utils';
|
||||
|
||||
const updateOptions = operations => options => {
|
||||
const resourceName = getResourceName( 'options', Object.keys( options ) );
|
||||
const resourceName = getResourceName( 'options-update', Object.keys( options ) );
|
||||
operations.update( [ resourceName ], { [ resourceName ]: options } );
|
||||
};
|
||||
|
||||
|
|
|
@ -40,21 +40,24 @@ function updateOptions( resourceNames, data, fetch ) {
|
|||
const url = WC_ADMIN_NAMESPACE + '/options';
|
||||
|
||||
const filteredNames = resourceNames.filter( name => {
|
||||
return name.startsWith( 'options' );
|
||||
return name.startsWith( 'options-update' );
|
||||
} );
|
||||
|
||||
return filteredNames.map( async resourceName => {
|
||||
return fetch( { path: url, method: 'POST', data: data[ resourceName ] } )
|
||||
.then( () => optionsToResource( data[ resourceName ] ) )
|
||||
.then( () => optionsToResource( data[ resourceName ], true ) )
|
||||
.catch( error => {
|
||||
return { [ resourceName ]: { error } };
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
function optionsToResource( options ) {
|
||||
function optionsToResource( options, updateResource = false ) {
|
||||
const optionNames = Object.keys( options );
|
||||
const resourceName = getResourceName( 'options', optionNames );
|
||||
const resourceName = getResourceName(
|
||||
updateResource ? 'options-update' : 'options',
|
||||
optionNames
|
||||
);
|
||||
const resources = {};
|
||||
|
||||
optionNames.forEach(
|
||||
|
|
|
@ -31,7 +31,7 @@ const getOptionsError = getResource => optionNames => {
|
|||
return getResource( getResourceName( 'options', optionNames ) ).error;
|
||||
};
|
||||
|
||||
const isOptionsRequesting = getResource => optionNames => {
|
||||
const isGetOptionsRequesting = getResource => optionNames => {
|
||||
const { lastReceived, lastRequested } = getResource( getResourceName( 'options', optionNames ) );
|
||||
|
||||
if ( ! isNil( lastRequested ) && isNil( lastReceived ) ) {
|
||||
|
@ -41,8 +41,21 @@ const isOptionsRequesting = getResource => optionNames => {
|
|||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
const isUpdateOptionsRequesting = getResource => optionNames => {
|
||||
const { lastReceived, lastRequested } = getResource(
|
||||
getResourceName( 'options-update', optionNames )
|
||||
);
|
||||
|
||||
if ( ! isNil( lastRequested ) && isNil( lastReceived ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lastRequested > lastReceived;
|
||||
};
|
||||
|
||||
export default {
|
||||
getOptions,
|
||||
getOptionsError,
|
||||
isOptionsRequesting,
|
||||
isGetOptionsRequesting,
|
||||
isUpdateOptionsRequesting,
|
||||
};
|
||||
|
|
|
@ -49,6 +49,19 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
|
|||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/create_homepage',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_homepage' ),
|
||||
'permission_callback' => array( $this, 'create_homepage_permission_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,6 +78,20 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to create a product.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function create_homepage_permission_check( $request ) {
|
||||
if ( ! wc_rest_check_post_permissions( 'page', 'create' ) || ! current_user_can( 'manage_options' ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create a new homepage.', 'woocommerce-admin' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import sample products from WooCommerce sample CSV.
|
||||
*
|
||||
|
@ -134,4 +161,32 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
|
|||
public static function sanitize_special_column_name_regex( $value ) {
|
||||
return '/' . str_replace( array( '%d', '%s' ), '(.*)', trim( quotemeta( $value ) ) ) . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a homepage from a template.
|
||||
*/
|
||||
public static function create_homepage() {
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_title' => __( 'Homepage', 'woocommerce-admin' ),
|
||||
'post_type' => 'page',
|
||||
'post_status' => 'draft',
|
||||
// @todo The images in this content should be replaced with working external links or imported.
|
||||
'post_content' => "<!-- wp:cover {\"url\":\"https://local.wordpress.test/wp-content/uploads/2019/05/parallax.jpeg\",\"id\":3624} -->\n<div class=\"wp-block-cover has-background-dim\" style=\"background-image:url(https://local.wordpress.test/wp-content/uploads/2019/05/parallax.jpeg)\"><div class=\"wp-block-cover__inner-container\"><!-- wp:paragraph {\"align\":\"center\",\"placeholder\":\"Write title…\",\"fontSize\":\"large\"} -->\n<p style=\"text-align:center\" class=\"has-large-font-size\">Welcome to the store</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph {\"align\":\"center\"} -->\n<p style=\"text-align:center\">Write a short welcome message here</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:button {\"align\":\"center\"} -->\n<div class=\"wp-block-button aligncenter\"><a class=\"wp-block-button__link\">Go shopping</a></div>\n<!-- /wp:button --></div></div>\n<!-- /wp:cover -->\n\n<!-- wp:heading {\"align\":\"center\"} -->\n<h2 style=\"text-align:center\">New products</h2>\n<!-- /wp:heading -->\n\n<!-- wp:woocommerce/product-new /-->\n\n<!-- wp:media-text {\"align\":\"\",\"backgroundColor\":\"light-gray\",\"mediaPosition\":\"right\",\"mediaId\":1257,\"mediaType\":\"image\"} -->\n<div class=\"wp-block-media-text has-media-on-the-right has-light-gray-background-color\"><figure class=\"wp-block-media-text__media\"><img src=\"https://local.wordpress.test/wp-content/uploads/2017/05/brady-bellini-191086-1024x616.jpg\" alt=\"\" class=\"wp-image-1257\"/></figure><div class=\"wp-block-media-text__content\"><!-- wp:paragraph {\"align\":\"center\",\"placeholder\":\"Content…\",\"fontSize\":\"large\"} -->\n<p style=\"text-align:center\" class=\"has-large-font-size\">Here's a business goal</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph {\"align\":\"center\"} -->\n<p style=\"text-align:center\">Describe your business aspiration here.</p>\n<!-- /wp:paragraph --></div></div>\n<!-- /wp:media-text -->\n\n<!-- wp:media-text {\"align\":\"\",\"backgroundColor\":\"light-gray\",\"mediaId\":1257,\"mediaType\":\"image\"} -->\n<div class=\"wp-block-media-text has-light-gray-background-color\"><figure class=\"wp-block-media-text__media\"><img src=\"https://local.wordpress.test/wp-content/uploads/2017/05/brady-bellini-191086-1024x616.jpg\" alt=\"\" class=\"wp-image-1257\"/></figure><div class=\"wp-block-media-text__content\"><!-- wp:paragraph {\"align\":\"center\",\"placeholder\":\"Content…\",\"fontSize\":\"large\"} -->\n<p style=\"text-align:center\" class=\"has-large-font-size\">Another business goal</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph {\"align\":\"center\"} -->\n<p style=\"text-align:center\">Describe your business aspiration here.</p>\n<!-- /wp:paragraph --></div></div>\n<!-- /wp:media-text -->\n\n<!-- wp:media-text {\"align\":\"\",\"backgroundColor\":\"light-gray\",\"mediaPosition\":\"right\",\"mediaId\":1257,\"mediaType\":\"image\"} -->\n<div class=\"wp-block-media-text has-media-on-the-right has-light-gray-background-color\"><figure class=\"wp-block-media-text__media\"><img src=\"https://local.wordpress.test/wp-content/uploads/2017/05/brady-bellini-191086-1024x616.jpg\" alt=\"\" class=\"wp-image-1257\"/></figure><div class=\"wp-block-media-text__content\"><!-- wp:paragraph {\"align\":\"center\",\"placeholder\":\"Content…\",\"fontSize\":\"large\"} -->\n<p style=\"text-align:center\" class=\"has-large-font-size\">A final business goal</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph {\"align\":\"center\"} -->\n<p style=\"text-align:center\">Describe your business aspiration here.</p>\n<!-- /wp:paragraph --></div></div>\n<!-- /wp:media-text -->\n\n<!-- wp:woocommerce/featured-product {\"editMode\":false,\"productId\":2567} -->\n<!-- wp:button {\"align\":\"center\"} -->\n<div class=\"wp-block-button aligncenter\"><a class=\"wp-block-button__link\" href=\"https://local.wordpress.test/shop/decor/wordpress-pennant\">Shop now</a></div>\n<!-- /wp:button -->\n<!-- /wp:woocommerce/featured-product -->"
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! is_wp_error( $post_id ) ) {
|
||||
update_option( 'woocommerce_onboarding_homepage_post_id', $post_id );
|
||||
|
||||
return array(
|
||||
'status' => 'success',
|
||||
'message' => __( 'Homepage created successfully.', 'woocommerce-admin' ),
|
||||
'post_id' => $post_id,
|
||||
'edit_post_link' => htmlspecialchars_decode( get_edit_post_link( $post_id ) ),
|
||||
);
|
||||
} else {
|
||||
return $post_id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,13 +26,6 @@ class OnboardingTasks {
|
|||
*/
|
||||
const ACTIVE_TASK_TRANSIENT = 'wc_onboarding_active_task';
|
||||
|
||||
/**
|
||||
* Name of the tasks transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TASKS_TRANSIENT = 'wc_onboarding_tasks';
|
||||
|
||||
/**
|
||||
* Get class instance.
|
||||
*/
|
||||
|
@ -50,7 +43,7 @@ class OnboardingTasks {
|
|||
add_action( 'admin_enqueue_scripts', array( $this, 'add_media_scripts' ) );
|
||||
add_action( 'woocommerce_components_settings', array( $this, 'component_settings' ), 30 ); // Run after Onboarding.
|
||||
add_action( 'admin_init', array( $this, 'set_active_task' ), 20 );
|
||||
add_action( 'admin_init', array( $this, 'check_active_task_completion' ), 1 );
|
||||
add_action( 'current_screen', array( $this, 'check_active_task_completion' ), 1000 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,26 +59,14 @@ class OnboardingTasks {
|
|||
* @param array $settings Component settings.
|
||||
*/
|
||||
public function component_settings( $settings ) {
|
||||
$tasks = get_transient( self::TASKS_TRANSIENT );
|
||||
$products = wp_count_posts( 'product' );
|
||||
|
||||
if ( ! $tasks ) {
|
||||
$tasks = array();
|
||||
$task_list = array( 'products' );
|
||||
|
||||
foreach ( $task_list as $task ) {
|
||||
$tasks[ $task ] = self::check_task_completion( $task );
|
||||
}
|
||||
|
||||
set_transient( self::TASKS_TRANSIENT, $tasks, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
// @todo We may want to consider caching some of these and use to check against
|
||||
// task completion along with cache busting for active tasks.
|
||||
$settings['onboarding']['automatedTaxSupportedCountries'] = self::get_automated_tax_supported_countries();
|
||||
$settings['onboarding']['customLogo'] = get_theme_mod( 'custom_logo', false );
|
||||
$settings['onboarding']['hasProducts'] = (int) $products->publish > 0 || (int) $products->draft > 0;
|
||||
$settings['onboarding']['tasks'] = $tasks;
|
||||
$settings['onboarding']['hasHomepage'] = self::check_task_completion( 'homepage' );
|
||||
$settings['onboarding']['hasProducts'] = self::check_task_completion( 'products' );
|
||||
$settings['onboarding']['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() );
|
||||
|
||||
return $settings;
|
||||
|
@ -122,7 +103,6 @@ class OnboardingTasks {
|
|||
|
||||
if ( self::check_task_completion( $active_task ) ) {
|
||||
delete_transient( self::ACTIVE_TASK_TRANSIENT );
|
||||
delete_transient( self::TASKS_TRANSIENT );
|
||||
wp_safe_redirect( wc_admin_url() );
|
||||
exit;
|
||||
}
|
||||
|
@ -139,6 +119,24 @@ class OnboardingTasks {
|
|||
case 'products':
|
||||
$products = wp_count_posts( 'product' );
|
||||
return (int) $products->publish > 0 || (int) $products->draft > 0;
|
||||
|
||||
case 'homepage':
|
||||
// @todo This should be run client-side in a Gutenberg hook and add a notice
|
||||
// to return to the task list if complete.
|
||||
$homepage_id = get_option( 'woocommerce_onboarding_homepage_post_id', false );
|
||||
|
||||
if ( ! $homepage_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post = get_post( $homepage_id );
|
||||
$completed = $post && 'publish' === $post->post_status;
|
||||
if ( $completed ) {
|
||||
update_option( 'show_on_front', 'page' );
|
||||
update_option( 'page_on_front', $homepage_id );
|
||||
}
|
||||
|
||||
return $completed;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -49,4 +49,20 @@ class WC_Tests_API_Onboarding_Tasks extends WC_REST_Unit_Test_Case {
|
|||
$this->assertArrayHasKey( 'skipped', $data );
|
||||
$this->assertArrayHasKey( 'updated', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Tasks data is returned by the endpoint.
|
||||
*/
|
||||
public function test_create_homepage() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/create_homepage' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertEquals( 'success', $data['status'] );
|
||||
$this->assertEquals( get_option( 'woocommerce_onboarding_homepage_post_id' ), $data['post_id'] );
|
||||
$this->assertEquals( htmlspecialchars_decode( get_edit_post_link( get_option( 'woocommerce_onboarding_homepage_post_id' ) ) ), $data['edit_post_link'] );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue