yf_manage_shop_product_edit admin module| File | plugins/shop/admin_modules/manage_shop/yf_manage_shop_product_edit.class.php |
|---|---|
| Use | admin URL ?object=manage_shop_product_edit |
A site sets them in its config with $PROJECT_CONF['manage_shop_product_edit']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$SPHINX_REINDEX_FLAG_FILES |
['/tmp/sphinx/indexer-kupi', '/tmp/sphinx/indexer-kupi_dev'] |
Touched after a product edit is saved, for an external Sphinx indexer; [] turns it off (default [] in 4.0). |
public function product_edit()
public function product_edit()
{
$id = (int) ($_GET['id'] ?? null);
$_GET['id'] = $id;
if (empty($id)) {
return _e('Empty id');
}
$product_info = module('manage_shop')->_product_get_info($id);
if (empty($product_info['id'])) {
return _e('Product not found');
}
// prepare region
$_region = _class('_shop_region', 'modules/shop/')->_get_list();
$region = _class('_shop_region', 'modules/shop/')->_get_by_product_ids($id, $force = true);
$region = $region[$id];
// -----
if (main()->is_post()) {
// TODO: use validation from form2()
if ( ! ($_POST['name'] ?? null)) {
_re('Product name must be filled', 'name');
}
if ( ! common()->_error_exists()) {
module('manage_shop')->_product_check_first_revision('product', $id);
$sql = [
'url' => ($_POST['url'] ?? null) ?: common()->_propose_url_from_name(($_POST['name'] ?? null)),
'active' => (int) ((bool) ($_POST['active'] ?? null)),
'update_date' => time(),
];
foreach (['name', 'description', 'model', 'articul', 'cat_id', 'meta_keywords', 'meta_desc', 'featured', 'external_url', 'sku', 'stock_status_id', 'manufacturer_id', 'supplier_id', 'quantity'] as $k) {
if (isset($_POST[$k])) {
$sql[$k] = ($_POST[$k] ?? null);
}
}
foreach (['price', 'price_promo', 'price_partner', 'price_raw', 'old_price'] as $k) {
if (isset($_POST[$k])) {
$sql[$k] = number_format(($_POST[$k] ?? 0.0), 2, '.', '');
}
}
db()->update_safe(db('shop_products'), $sql, 'id=' . $id);
if ( ! empty($_FILES)) {
module('manage_shop')->_product_image_upload($id);
}
$params_to_insert = [];
foreach ((array) ($_POST['productparams'] ?? null) as $param_id) {
db()->query('DELETE FROM ' . db('shop_products_productparams') . ' WHERE product_id=' . $id);
$param_id = (int) $param_id;
if ( ! $param_id) {
continue;
}
foreach ((array) ($_POST['productparams_options_' . $param_id] ?? null) as $v) {
$params_to_insert[] = [
'product_id' => $id,
'productparam_id' => $param_id,
'value' => $v,
];
}
}
if ($params_to_insert) {
db()->insert_safe('shop_products_productparams', $params_to_insert);
}
$product_to_category_insert = [];
foreach ((array) ($_POST['category'] ?? null) as $_cat_id) {
$_cat_id = (int) $_cat_id;
if ( ! $_cat_id) {
continue;
}
$product_to_category_insert[] = [
'product_id' => $id,
'category_id' => $_cat_id,
];
}
if ($product_to_category_insert) {
db()->query('DELETE FROM ' . db('shop_product_to_category') . ' WHERE product_id=' . $id);
db()->insert_safe(db('shop_product_to_category'), $product_to_category_insert);
}
$product_related_insert = [];
foreach ((array) ($_POST['product_related'] ?? null) as $related_id) {
$related_id = (int) $related_id;
if ( ! $related_id) {
continue;
}
$product_related_insert[] = [
'product_id' => $id,
'related_id' => $related_id,
];
}
if ($product_related_insert) {
db()->query('DELETE FROM ' . db('shop_product_related') . ' WHERE product_id=' . $id);
db()->insert_safe(db('shop_product_related'), $product_related_insert);
}
// update region
$_table = 'shop_product_to_region';
$_post = _class('_shop_region', 'modules/shop/')->_check_by_product_id(($_POST['region'] ?? null));
$_insert = array_diff($_post, $region);
$_delete = array_diff($region, $_post);
// insert
if ( ! empty($_insert)) {
$_data = [];
foreach ($_insert as $_id) {
$_data[] = ['product_id' => $id, 'region_id' => $_id];
}
db_query('START TRANSACTION');
db()->insert_on_duplicate_key_update($_table, $_data);
db_query('COMMIT');
}
// delete
if ( ! empty($_delete) && (int) $id > 0) {
db_query('START TRANSACTION');
db()->from($_table)
->where('product_id', '=', (int) $id)
->where('region_id', 'in', array_map('intval', $_delete))
->delete();
db_query('COMMIT');
}
$region = _class('_shop_region', 'modules/shop/')->_get_by_product_ids($id, true);
$region = $region[$id];
// -----
$product_to_unit_insert = [];
foreach ((array) ($_POST['units'] ?? null) as $_unit_id) {
$_unit_id = (int) $_unit_id;
if (empty($_unit_id)) {
continue;
}
$product_to_unit_insert[] = [
'product_id' => $id,
'unit_id' => $_unit_id,
];
}
db()->query('DELETE FROM ' . db('shop_product_to_unit') . ' WHERE product_id=' . $id);
if ($product_to_unit_insert) {
db()->insert_safe(db('shop_product_to_unit'), $product_to_unit_insert);
}
module('manage_shop')->_attributes_save($id);
module('manage_shop')->_product_add_revision('edit', $id);
module('manage_shop')->_product_cache_purge($id);
common()->admin_wall_add(['shop product updated: ' . ($_POST['name'] ?? null), $id]);
$this->_product_saved($id);
}
return js_redirect('./?object=' . main()->_get('object') . '&action=product_edit&id=' . $id);
}
$media_host = defined('MEDIA_HOST') ? MEDIA_HOST : false;
$base_url = WEB_PATH;
if ( ! empty($media_host)) {
$base_url = '//' . $media_host . '/';
}
$images_items = [];
foreach ((array) common()->shop_get_images($product_info['id']) as $a) {
$images_items[] = tpl()->parse('manage_shop/image_items', [
'img_path' => $base_url . $a['big'],
'thumb_path' => $base_url . $a['thumb'],
'del_url' => './?object=' . main()->_get('object') . '&action=product_image_delete&id=' . $product_info['id'] . '&key=' . $a['id'],
'image_key' => $a['id'],
'data-test' => 'delete_image_btn',
]);
}
$products_to_category = [];
foreach ((array) db()->get_all('SELECT category_id FROM ' . db('shop_product_to_category') . ' WHERE product_id=' . $id) as $a) {
$products_to_category[$a['category_id']] = $a['category_id'];
}
$products_to_unit = [];
foreach ((array) db()->get_all('SELECT unit_id FROM ' . db('shop_product_to_unit') . ' WHERE product_id=' . $id) as $a) {
$products_to_unit[$a['unit_id']] = $a['unit_id'];
}
$replace = $product_info + [
'form_action' => './?object=' . main()->_get('object') . '&action=product_edit&id=' . $product_info['id'],
'back_url' => './?object=' . main()->_get('object') . '&action=products',
'units' => $products_to_unit,
];
$textarea_id = 'description';
return form($replace, [
// TODO: use validation
'for_upload' => 1,
'currency' => module('manage_shop')->CURRENCY,
'hide_empty' => 1,
'tabs' => [
'class' => 'span6 col-md-6',
'show_all' => 1,
'no_headers' => 1,
],
])
->tab_start('tab_desc', ['tab_body' => ['class' => 'active span12 col-md-12']])
->textarea('description', ['style' => 'min-width:100%', 'cols' => 200, 'rows' => 10, 'ckeditor' => ['config' => _class('admin_methods')->_get_cke_config()]])
->tab_end()
->tab_start('main')
->link('product_url_user', url_user('/shop/product/' . $product_info['id']), ['target' => '_blank'])
->info('id')
->text('name')
->text('articul')
->text('url')
->chosen_box('cat_id', module('manage_shop')->_cats_for_select, ['desc' => 'Main category', 'edit_link' => './?object=category_editor&action=show_items&id=shop_cats', 'translate' => 0, 'data-test' => 'select_category'])
// TODO: replace with similar JS container as for params and images
// ->multi_select('category', module('manage_shop')->_cats_for_select, array('desc' => 'Secondary categories', 'edit_link' => './?object=category_editor&action=show_items&id=shop_cats', 'selected' => $products_to_category, 'translate' => 0))
->chosen_box('manufacturer_id', module('manage_shop')->_man_for_select, ['desc' => 'Manufacturer', 'edit_link' => './?object=' . main()->_get('object') . '&action=manufacturers', 'translate' => 0, 'data-test' => 'select_manufacturer'])
->chosen_box('supplier_id', module('manage_shop')->_suppliers_for_select, ['desc' => 'Supplier', 'edit_link' => './?object=' . main()->_get('object') . '&action=suppliers', 'data-test' => 'select_supplier'])
->select2_box([
'desc' => 'Region',
'name' => 'region',
'multiple' => true,
'values' => $_region,
'selected' => $region,
'edit_link' => url_admin('/manage_shop/region'),
'data-test' => 'select_region',
])
->number('quantity', ['min' => 0])
->active_box('active')
->tab_end()
->tab_start('params')
->container(
($images_items ? implode(PHP_EOL, $images_items) : '') .
'<a class="btn btn-default btn-mini btn-xs" data-test="add_image" onclick="addImage();"><span>' . t('Add Image') . '</span></a> <div id="images"></div>',
['desc' => 'Images']
)
->link('Set main image', './?object=' . ($_GET['object'] ?? null) . '&action=set_main_image&id=' . $product_info['id'], [
'class_add' => 'ajax_edit',
'data-test' => 'set_main_image_btn',
'display_func' => function () use ($images_items) {
return is_array($images_items) && count((array) $images_items) > 1;
},
])
->container(module('manage_shop')->_productparams_container($id), ['desc' => 'Product params'/*, 'edit_link' => './?object='.main()->_get('object').'&action=attributes'*/])
// TODO: replace with similar JS container as for params and images
// ->container(module('manage_shop')->related_products($product_info['id']), array('desc' => 'Related products'))
// ->multi_select2_box('units', module('manage_shop')->_units_for_select, array('desc' => 'Units', 'edit_link' => './?object='.main()->_get('object').'&action=units', 'show_text' => 1))
->select2_box([
'desc' => 'Unit',
'name' => 'units',
'multiple' => true,
'values' => module('manage_shop')->_units_for_select,
'edit_link' => url_admin('/manage_shop/units'),
'data-test' => 'select_units',
])
->price('old_price')
->price('price')
->price('price_promo')
->price('price_partner')
->price('price_raw')
->tab_end()
->tab_start('tab_save', ['tab_body' => ['class' => 'active span12 col-md-12']])
->save_and_back()
->tab_end()
. tpl()->parse('manage_shop/product_edit_js');
}
_ hooks and helpers; not routable as page actionspublic function _product_saved($id)
Runs after a product edit is saved: touches SPHINX_REINDEX_FLAG_FILES, then fires `shop.product_saved` ($id, 'edit'). Listeners run after the data is written and must not throw: an exception skips the redirect, not the save.
$id int public function _product_saved($id)
{
$this->_touch_reindex_flag_files();
events()->fire('shop.product_saved', [(int) $id, 'edit']);
}
public function _touch_reindex_flag_files()
Same effect as the shell `touch` it replaces: an existing file is always tried (its owner may touch it read-only), a missing file only in a writable directory, a missing directory is never created, and a failure stays quiet (exec() never warned; `@touch()` would still reach the yf error log, hence the local handler).
public function _touch_reindex_flag_files()
{
foreach ((array) $this->SPHINX_REINDEX_FLAG_FILES as $file) {
$file = (string) $file;
if ($file === '' || ( ! file_exists($file) && ! is_writable(dirname($file)))) {
continue;
}
set_error_handler(fn () => true);
try {
touch($file);
} finally {
restore_error_handler();
}
}
}