F-002 fix: Remove secrets and externalize config
This commit is contained in:
13
project/web/index/new/README.md
Normal file
13
project/web/index/new/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Legacy PHP module
|
||||
|
||||
Important paths:
|
||||
- `bootstrap.php` — shared config loader
|
||||
- `config/local.example.php` — copy template
|
||||
- `config/local.php` — real local values, ignored by git
|
||||
- `db/conn.php` — shared DB connection helper
|
||||
- `worker_bulk.php` — CLI worker
|
||||
|
||||
Setup:
|
||||
1. Review `config/README.md`.
|
||||
2. Fill `config/local.php` with local values.
|
||||
3. Import `project/sql/db-25052026.sql` into local MariaDB if needed.
|
||||
140
project/web/index/new/bootstrap.php
Normal file
140
project/web/index/new/bootstrap.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
if (defined('LEGACY_MODULE_BOOTSTRAPPED')) {
|
||||
return;
|
||||
}
|
||||
|
||||
define('LEGACY_MODULE_BOOTSTRAPPED', true);
|
||||
define('LEGACY_MODULE_ROOT', __DIR__);
|
||||
define('LEGACY_MODULE_CONFIG_DIR', LEGACY_MODULE_ROOT . '/config');
|
||||
define('LEGACY_MODULE_LOCAL_CONFIG', LEGACY_MODULE_CONFIG_DIR . '/local.php');
|
||||
define('LEGACY_MODULE_EXAMPLE_CONFIG', LEGACY_MODULE_CONFIG_DIR . '/local.example.php');
|
||||
|
||||
function legacy_default_config() {
|
||||
return [
|
||||
'db' => [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 3306,
|
||||
'database' => '',
|
||||
'user' => '',
|
||||
'password' => '',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
'openai' => [
|
||||
'api_key' => '',
|
||||
'model' => 'gpt-4o-mini',
|
||||
'endpoint' => 'https://api.openai.com/v1/chat/completions',
|
||||
],
|
||||
'store' => [
|
||||
'name' => 'Natural - Mercado de Vida',
|
||||
'language_es' => 4,
|
||||
'language_en' => 1,
|
||||
'image_base_url' => 'https://example.local/image/',
|
||||
'product_base_url' => 'https://example.local/index.php?route=product/product&product_id=',
|
||||
],
|
||||
'routes' => [
|
||||
'login_url' => '../login.php',
|
||||
'success_url' => 'https://example.local/producto-nuevo/success.php',
|
||||
],
|
||||
'security' => [
|
||||
'form_password_hash' => '',
|
||||
],
|
||||
'paths' => [
|
||||
'log_dir' => LEGACY_MODULE_ROOT . '/logs',
|
||||
'worker_log' => LEGACY_MODULE_ROOT . '/logs/worker.log',
|
||||
'prompt_en' => LEGACY_MODULE_ROOT . '/inc/prompt_en.md',
|
||||
'prompt_es' => LEGACY_MODULE_ROOT . '/inc/prompt_es.md',
|
||||
],
|
||||
'worker' => [
|
||||
'batch_size' => 20,
|
||||
'min_html_length' => 500,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function legacy_deep_merge(array $base, array $override) {
|
||||
foreach ($override as $key => $value) {
|
||||
if (is_array($value) && isset($base[$key]) && is_array($base[$key])) {
|
||||
$base[$key] = legacy_deep_merge($base[$key], $value);
|
||||
continue;
|
||||
}
|
||||
$base[$key] = $value;
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
function legacy_config_file_data($path) {
|
||||
if (!is_file($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = require $path;
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
function legacy_normalize_config(array $config) {
|
||||
foreach (['image_base_url', 'product_base_url'] as $key) {
|
||||
if (isset($config['store'][$key])) {
|
||||
$config['store'][$key] = trim((string)$config['store'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['routes']['login_url'])) {
|
||||
$config['routes']['login_url'] = trim((string)$config['routes']['login_url']);
|
||||
}
|
||||
|
||||
if (isset($config['routes']['success_url'])) {
|
||||
$config['routes']['success_url'] = trim((string)$config['routes']['success_url']);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
$legacy_config = legacy_default_config();
|
||||
$legacy_config_source = LEGACY_MODULE_EXAMPLE_CONFIG;
|
||||
|
||||
if (is_file(LEGACY_MODULE_LOCAL_CONFIG)) {
|
||||
$legacy_config = legacy_deep_merge($legacy_config, legacy_config_file_data(LEGACY_MODULE_LOCAL_CONFIG));
|
||||
$legacy_config_source = LEGACY_MODULE_LOCAL_CONFIG;
|
||||
} elseif (is_file(LEGACY_MODULE_EXAMPLE_CONFIG)) {
|
||||
$legacy_config = legacy_deep_merge($legacy_config, legacy_config_file_data(LEGACY_MODULE_EXAMPLE_CONFIG));
|
||||
}
|
||||
|
||||
$legacy_config = legacy_normalize_config($legacy_config);
|
||||
|
||||
function legacy_config_all() {
|
||||
global $legacy_config;
|
||||
return $legacy_config;
|
||||
}
|
||||
|
||||
function legacy_config($path, $default = null) {
|
||||
$value = legacy_config_all();
|
||||
foreach (explode('.', $path) as $segment) {
|
||||
if (!is_array($value) || !array_key_exists($segment, $value)) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$segment];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
function legacy_config_source() {
|
||||
global $legacy_config_source;
|
||||
return $legacy_config_source;
|
||||
}
|
||||
|
||||
function legacy_new_mysqli() {
|
||||
$host = legacy_config('db.host', '127.0.0.1');
|
||||
$user = legacy_config('db.user', '');
|
||||
$password = legacy_config('db.password', '');
|
||||
$database = legacy_config('db.database', '');
|
||||
$port = (int) legacy_config('db.port', 3306);
|
||||
$charset = legacy_config('db.charset', 'utf8');
|
||||
|
||||
$db = new mysqli($host, $user, $password, $database, $port);
|
||||
if (!$db->connect_errno && $charset) {
|
||||
$db->set_charset($charset);
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
17
project/web/index/new/config/README.md
Normal file
17
project/web/index/new/config/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Local config setup
|
||||
|
||||
1. Copy `local.example.php` to `local.php`.
|
||||
2. Fill real local DB, OpenAI, and URL values in `local.php`.
|
||||
3. Keep `local.php` out of git.
|
||||
|
||||
Config keys used by the legacy module:
|
||||
- `db.*`
|
||||
- `openai.*`
|
||||
- `store.*`
|
||||
- `routes.*`
|
||||
- `security.form_password_hash`
|
||||
- `worker.*`
|
||||
|
||||
The module loads:
|
||||
- real local values from `config/local.php`
|
||||
- safe fallback values from `config/local.example.php`
|
||||
35
project/web/index/new/config/local.example.php
Normal file
35
project/web/index/new/config/local.example.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'db' => [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 3306,
|
||||
'database' => 'CHANGE_ME_DB_NAME',
|
||||
'user' => 'CHANGE_ME_DB_USER',
|
||||
'password' => 'CHANGE_ME_DB_PASSWORD',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
'openai' => [
|
||||
'api_key' => 'CHANGE_ME_OPENAI_API_KEY',
|
||||
'model' => 'gpt-4o-mini',
|
||||
'endpoint' => 'https://api.openai.com/v1/chat/completions',
|
||||
],
|
||||
'store' => [
|
||||
'name' => 'Natural - Mercado de Vida',
|
||||
'language_es' => 4,
|
||||
'language_en' => 1,
|
||||
'image_base_url' => 'https://example.local/image/',
|
||||
'product_base_url' => 'https://example.local/index.php?route=product/product&product_id=',
|
||||
],
|
||||
'routes' => [
|
||||
'login_url' => '../login.php',
|
||||
'success_url' => 'https://example.local/producto-nuevo/success.php',
|
||||
],
|
||||
'security' => [
|
||||
'form_password_hash' => 'CHANGE_ME_FORM_PASSWORD_HASH',
|
||||
],
|
||||
'worker' => [
|
||||
'batch_size' => 20,
|
||||
'min_html_length' => 500,
|
||||
],
|
||||
];
|
||||
396
project/web/index/new/css/custom.css
Normal file
396
project/web/index/new/css/custom.css
Normal file
@@ -0,0 +1,396 @@
|
||||
option:disabled {
|
||||
font-weight: bolder;
|
||||
color: #FF5722;
|
||||
}
|
||||
|
||||
#atrib_zone, #edit_zone {
|
||||
border: #ccc 3px dashed;
|
||||
padding: 15px 15px 0px 15px;
|
||||
}
|
||||
|
||||
.ck-editor__editable_inline {
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
input[name=modelo], input[name=url] {
|
||||
pointer-events: none;
|
||||
background: whitesmoke;
|
||||
color: #FF5722;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
#imgPreview {
|
||||
text-align: center;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.infoText {
|
||||
font-size: small;
|
||||
font-weight: bolder;
|
||||
color: #FF5722;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.uploadImg {
|
||||
border: 1px solid #ccc;
|
||||
/*display: inline-block;*/
|
||||
padding: 20px 12px 6px 12px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
height: 37px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#imgElement {
|
||||
max-width: 170px;
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
#crearProducto {
|
||||
height: 110px;
|
||||
}
|
||||
|
||||
#loading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#ia_link {
|
||||
/* display: none; */
|
||||
background-color: #FFC107;
|
||||
border-color: #FFC107;
|
||||
}
|
||||
|
||||
/* Definimos la animación de parpadeo */
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Aplicamos la animación al elemento */
|
||||
#ia_link {
|
||||
animation: blink 2s ease-in-out infinite;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Agregamos un efecto de transición suave */
|
||||
#ia_link:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Natural - Mercado de Vida
|
||||
Panel de administración SEO y descripciones
|
||||
Complemento para Skeleton Boilerplate
|
||||
============================================================ */
|
||||
|
||||
/* ---------- Variables ---------- */
|
||||
:root {
|
||||
--color-primary: #0074D9;
|
||||
--color-success: #28a745;
|
||||
--color-danger: #dc3545;
|
||||
--color-warning: #ffae00;
|
||||
--color-light: #f4f4f4;
|
||||
--color-muted: #666;
|
||||
}
|
||||
|
||||
/* ---------- Contenedores ---------- */
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 0 50px;
|
||||
}
|
||||
|
||||
/* ---------- Tablas ---------- */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 3px rgba(0,0,0,0.05);
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--color-light);
|
||||
text-align: left;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
tr:hover td {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
/* ---------- Imágenes ---------- */
|
||||
td img {
|
||||
width: 140px;
|
||||
border-radius: 6px;
|
||||
display: block;
|
||||
margin: 0 auto 8px;
|
||||
}
|
||||
|
||||
/* ---------- Textos de idiomas ---------- */
|
||||
.lang-title {
|
||||
font-weight: bold;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.lang-section {
|
||||
line-height: 1.5em;
|
||||
font-size: 1rem;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* ---------- Badges ---------- */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: .85rem;
|
||||
color: #fff;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.badge.ok {
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.badge.miss {
|
||||
background: var(--color-danger);
|
||||
}
|
||||
|
||||
.badge.warn {
|
||||
background: var(--color-warning);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* ---------- Botones ---------- */
|
||||
.button-primary,
|
||||
button.button-primary {
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.button-primary:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Botón de verificación manual */
|
||||
.toggle-btn {
|
||||
background: var(--color-warning);
|
||||
color: #000;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 6px;
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: var(--color-success);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Toolbar superior */
|
||||
.toolbar {
|
||||
margin: 10px 0 15px;
|
||||
}
|
||||
|
||||
.toolbar button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* ---------- Caja de ID y estado ---------- */
|
||||
.idbox {
|
||||
font-size: .9rem;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.idbox small {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-actions a {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.alert-box {
|
||||
background: #f5f8fb;
|
||||
border: 1px solid #d9e3ec;
|
||||
border-radius: 6px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.table-description {
|
||||
max-height: 120px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.log-panel {
|
||||
margin-top: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.log-panel__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.log-panel__status {
|
||||
font-size: 0.95rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.log-panel__body {
|
||||
margin: 0;
|
||||
padding: 14px;
|
||||
max-height: 360px;
|
||||
overflow: auto;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.45;
|
||||
background: #111;
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
.pagination-row {
|
||||
margin-top: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pagination-row .button {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.pagination-row .button.disabled {
|
||||
pointer-events: none;
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
.pagination-row .pagination-meta {
|
||||
margin: 0 12px;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
/* ---------- Loader ---------- */
|
||||
.loader-container {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 5px solid #eee;
|
||||
border-top: 5px solid var(--color-primary);
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
animation: fadeText 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeText {
|
||||
0%,100% { opacity: 0.2; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* ---------- Paginación ---------- */
|
||||
.pagination {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.pagination a {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
margin: 0 4px;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pagination a.disabled {
|
||||
background: #ccc;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pagination span {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ---------- Responsive ---------- */
|
||||
@media (max-width: 768px) {
|
||||
td img { width: 100px; }
|
||||
table, th, td { font-size: 1.3rem; }
|
||||
.toolbar { text-align: center; }
|
||||
.lang-section { font-size: 1.3rem;
|
||||
text-align: justify;}
|
||||
}
|
||||
|
||||
.last {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.checkbox-label .label-body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Alineación vertical suave y estética Skeleton */
|
||||
#missing {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
label[for="missing"] {
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
color: #555;
|
||||
}
|
||||
39
project/web/index/new/css/modal.css
Normal file
39
project/web/index/new/css/modal.css
Normal file
@@ -0,0 +1,39 @@
|
||||
/* The Modal (background) */
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
padding-top: 100px; /* Location of the box */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
/* Modal Content */
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
max-width: 750px;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
/* The Close Button */
|
||||
.close {
|
||||
color: #aaaaaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
427
project/web/index/new/css/normalize.css
vendored
Normal file
427
project/web/index/new/css/normalize.css
vendored
Normal file
@@ -0,0 +1,427 @@
|
||||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
||||
* and Firefox.
|
||||
* Correct `block` display not defined for `main` in IE 11.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9/10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow not hidden in IE 9/10/11.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contain overflow in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address odd `em`-unit font size rendering in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||
* styling of `select`, unless a `border` property is set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited.
|
||||
* Known issue: affects color of disabled elements.
|
||||
* 2. Correct font properties not being inherited.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||
* Correct `select` style inheritance in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's recommended that you don't attempt to style these elements.
|
||||
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
||||
*
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
||||
* `font-size` values of the `input`, it causes the cursor style of the
|
||||
* decrement button to change from `default` to `text`.
|
||||
*/
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||
* padding (and `textfield` appearance).
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't inherit the `font-weight` (applied by a rule above).
|
||||
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
||||
*/
|
||||
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
427
project/web/index/new/css/skeleton.css
vendored
Normal file
427
project/web/index/new/css/skeleton.css
vendored
Normal file
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Skeleton V2.0.4
|
||||
* Copyright 2014, Dave Gamache
|
||||
* www.getskeleton.com
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* 12/29/2014
|
||||
*/
|
||||
|
||||
|
||||
/* Table of contents
|
||||
––––––––––––––––––––––––––––––––––––––––––––––––––
|
||||
- Grid
|
||||
- Base Styles
|
||||
- Typography
|
||||
- Links
|
||||
- Buttons
|
||||
- Forms
|
||||
- Lists
|
||||
- Code
|
||||
- Tables
|
||||
- Spacing
|
||||
- Utilities
|
||||
- Clearing
|
||||
- Media Queries
|
||||
*/
|
||||
|
||||
|
||||
/* Grid
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box; }
|
||||
.column,
|
||||
.columns {
|
||||
width: 100%;
|
||||
float: left;
|
||||
box-sizing: border-box; }
|
||||
|
||||
/* For devices larger than 400px */
|
||||
@media (min-width: 400px) {
|
||||
.container {
|
||||
width: 85%;
|
||||
padding: 0; }
|
||||
}
|
||||
|
||||
/* For devices larger than 550px */
|
||||
@media (min-width: 550px) {
|
||||
.container {
|
||||
width: 80%; }
|
||||
.column,
|
||||
.columns {
|
||||
margin-left: 4%; }
|
||||
.column:first-child,
|
||||
.columns:first-child {
|
||||
margin-left: 0; }
|
||||
|
||||
.one.column,
|
||||
.one.columns { width: 4.66666666667%; }
|
||||
.two.columns { width: 13.3333333333%; }
|
||||
.three.columns { width: 22%; }
|
||||
.four.columns { width: 30.6666666667%; }
|
||||
.five.columns { width: 39.3333333333%; }
|
||||
.six.columns { width: 48%; }
|
||||
.seven.columns { width: 56.6666666667%; }
|
||||
.eight.columns { width: 65.3333333333%; }
|
||||
.nine.columns { width: 74.0%; }
|
||||
.ten.columns { width: 82.6666666667%; }
|
||||
.eleven.columns { width: 91.3333333333%; }
|
||||
.twelve.columns { width: 100%; margin-left: 0; }
|
||||
|
||||
.one-third.column { width: 30.6666666667%; }
|
||||
.two-thirds.column { width: 65.3333333333%; }
|
||||
|
||||
.one-half.column { width: 48%; }
|
||||
|
||||
/* Offsets */
|
||||
.offset-by-one.column,
|
||||
.offset-by-one.columns { margin-left: 8.66666666667%; }
|
||||
.offset-by-two.column,
|
||||
.offset-by-two.columns { margin-left: 17.3333333333%; }
|
||||
.offset-by-three.column,
|
||||
.offset-by-three.columns { margin-left: 26%; }
|
||||
.offset-by-four.column,
|
||||
.offset-by-four.columns { margin-left: 34.6666666667%; }
|
||||
.offset-by-five.column,
|
||||
.offset-by-five.columns { margin-left: 43.3333333333%; }
|
||||
.offset-by-six.column,
|
||||
.offset-by-six.columns { margin-left: 52%; }
|
||||
.offset-by-seven.column,
|
||||
.offset-by-seven.columns { margin-left: 60.6666666667%; }
|
||||
.offset-by-eight.column,
|
||||
.offset-by-eight.columns { margin-left: 69.3333333333%; }
|
||||
.offset-by-nine.column,
|
||||
.offset-by-nine.columns { margin-left: 78.0%; }
|
||||
.offset-by-ten.column,
|
||||
.offset-by-ten.columns { margin-left: 86.6666666667%; }
|
||||
.offset-by-eleven.column,
|
||||
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
|
||||
|
||||
.offset-by-one-third.column,
|
||||
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
|
||||
.offset-by-two-thirds.column,
|
||||
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
|
||||
|
||||
.offset-by-one-half.column,
|
||||
.offset-by-one-half.columns { margin-left: 52%; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Base Styles
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
/* NOTE
|
||||
html is set to 62.5% so that all the REM measurements throughout Skeleton
|
||||
are based on 10px sizing. So basically 1.5rem = 15px :) */
|
||||
html {
|
||||
font-size: 62.5%; }
|
||||
body {
|
||||
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
|
||||
line-height: 1.6;
|
||||
font-weight: 400;
|
||||
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #222; }
|
||||
|
||||
|
||||
/* Typography
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 2rem;
|
||||
font-weight: 300; }
|
||||
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
|
||||
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
|
||||
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
|
||||
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
|
||||
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
|
||||
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
|
||||
|
||||
/* Larger than phablet */
|
||||
@media (min-width: 550px) {
|
||||
h1 { font-size: 5.0rem; }
|
||||
h2 { font-size: 4.2rem; }
|
||||
h3 { font-size: 3.6rem; }
|
||||
h4 { font-size: 3.0rem; }
|
||||
h5 { font-size: 2.4rem; }
|
||||
h6 { font-size: 1.5rem; }
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0; }
|
||||
|
||||
|
||||
/* Links
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
a {
|
||||
color: #1EAEDB; }
|
||||
a:hover {
|
||||
color: #0FA0CE; }
|
||||
|
||||
|
||||
/* Buttons
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
.button,
|
||||
button,
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"] {
|
||||
display: inline-block;
|
||||
height: 38px;
|
||||
padding: 0 30px;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 38px;
|
||||
letter-spacing: .1rem;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #bbb;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box; }
|
||||
.button:hover,
|
||||
button:hover,
|
||||
input[type="submit"]:hover,
|
||||
input[type="reset"]:hover,
|
||||
input[type="button"]:hover,
|
||||
.button:focus,
|
||||
button:focus,
|
||||
input[type="submit"]:focus,
|
||||
input[type="reset"]:focus,
|
||||
input[type="button"]:focus {
|
||||
color: #333;
|
||||
border-color: #888;
|
||||
outline: 0; }
|
||||
.button.button-primary,
|
||||
button.button-primary,
|
||||
input[type="submit"].button-primary,
|
||||
input[type="reset"].button-primary,
|
||||
input[type="button"].button-primary {
|
||||
color: #FFF;
|
||||
background-color: #70ad47;
|
||||
border-color: #70ad47; }
|
||||
.button.button-primary:hover,
|
||||
button.button-primary:hover,
|
||||
input[type="submit"].button-primary:hover,
|
||||
input[type="reset"].button-primary:hover,
|
||||
input[type="button"].button-primary:hover,
|
||||
.button.button-primary:focus,
|
||||
button.button-primary:focus,
|
||||
input[type="submit"].button-primary:focus,
|
||||
input[type="reset"].button-primary:focus,
|
||||
input[type="button"].button-primary:focus {
|
||||
color: #FFF;
|
||||
background-color: #70ad47de;
|
||||
border-color: #70ad47de; }
|
||||
|
||||
|
||||
/* Forms
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="date"],
|
||||
input[type="search"],
|
||||
input[type="text"],
|
||||
input[type="color"],
|
||||
input[type="tel"],
|
||||
input[type="url"],
|
||||
input[type="password"],
|
||||
input[type="file"],
|
||||
textarea,
|
||||
select {
|
||||
height: 38px;
|
||||
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
|
||||
background-color: #fff;
|
||||
border: 1px solid #D1D1D1;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box; }
|
||||
/* Removes awkward default styles on some inputs for iOS */
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="date"],
|
||||
input[type="search"],
|
||||
input[type="text"],
|
||||
input[type="color"],
|
||||
input[type="tel"],
|
||||
input[type="url"],
|
||||
input[type="password"],
|
||||
input[type="file"],
|
||||
textarea {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none; }
|
||||
textarea {
|
||||
min-height: 65px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px; }
|
||||
input[type="email"]:focus,
|
||||
input[type="number"]:focus,
|
||||
input[type="date"]:focus,
|
||||
input[type="search"]:focus,
|
||||
input[type="text"]:focus,
|
||||
input[type="color"]:focus,
|
||||
input[type="tel"]:focus,
|
||||
input[type="url"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="file"]:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border: 1px solid #70ad47;
|
||||
outline: 0; }
|
||||
label,
|
||||
legend {
|
||||
display: block;
|
||||
margin-bottom: .5rem;
|
||||
font-weight: 600; }
|
||||
fieldset {
|
||||
padding: 0;
|
||||
border-width: 0; }
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
display: inline; }
|
||||
label > .label-body {
|
||||
display: inline-block;
|
||||
margin-left: .5rem;
|
||||
font-weight: normal; }
|
||||
|
||||
|
||||
/* Lists
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
ul {
|
||||
list-style: circle inside; }
|
||||
ol {
|
||||
list-style: decimal inside; }
|
||||
ol, ul {
|
||||
padding-left: 0;
|
||||
margin-top: 0; }
|
||||
ul ul,
|
||||
ul ol,
|
||||
ol ol,
|
||||
ol ul {
|
||||
margin: 1.5rem 0 1.5rem 3rem;
|
||||
font-size: 90%; }
|
||||
li {
|
||||
margin-bottom: 1rem; }
|
||||
|
||||
|
||||
/* Code
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
code {
|
||||
padding: .2rem .5rem;
|
||||
margin: 0 .2rem;
|
||||
font-size: 90%;
|
||||
white-space: nowrap;
|
||||
background: #F1F1F1;
|
||||
border: 1px solid #E1E1E1;
|
||||
border-radius: 4px; }
|
||||
pre > code {
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
white-space: pre; }
|
||||
|
||||
|
||||
/* Tables
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
th,
|
||||
td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #E1E1E1; }
|
||||
th:first-child,
|
||||
td:first-child {
|
||||
padding-left: 0; }
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
padding-right: 0; }
|
||||
|
||||
|
||||
/* Spacing
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
button,
|
||||
.button {
|
||||
margin-bottom: 1rem; }
|
||||
input,
|
||||
textarea,
|
||||
select,
|
||||
fieldset {
|
||||
margin-bottom: 1.5rem; }
|
||||
pre,
|
||||
blockquote,
|
||||
dl,
|
||||
figure,
|
||||
table,
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
form {
|
||||
margin-bottom: 2.5rem; }
|
||||
|
||||
|
||||
/* Utilities
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
.u-full-width {
|
||||
width: 100%;
|
||||
box-sizing: border-box; }
|
||||
.u-max-full-width {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box; }
|
||||
.u-pull-right {
|
||||
float: right; }
|
||||
.u-pull-left {
|
||||
float: left; }
|
||||
|
||||
|
||||
/* Misc
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
hr {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 3.5rem;
|
||||
border-width: 0;
|
||||
border-top: 1px solid #E1E1E1; }
|
||||
|
||||
|
||||
/* Clearing
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
|
||||
/* Self Clearing Goodness */
|
||||
.container:after,
|
||||
.row:after,
|
||||
.u-cf {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both; }
|
||||
|
||||
|
||||
/* Media Queries
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
/*
|
||||
Note: The best way to structure the use of media queries is to create the queries
|
||||
near the relevant code. For example, if you wanted to change the styles for buttons
|
||||
on small devices, paste the mobile query code up in the buttons section and style it
|
||||
there.
|
||||
*/
|
||||
|
||||
|
||||
/* Larger than mobile */
|
||||
@media (min-width: 400px) {}
|
||||
|
||||
/* Larger than phablet (also point when grid becomes active) */
|
||||
@media (min-width: 550px) {}
|
||||
|
||||
/* Larger than tablet */
|
||||
@media (min-width: 750px) {}
|
||||
|
||||
/* Larger than desktop */
|
||||
@media (min-width: 1000px) {}
|
||||
|
||||
/* Larger than Desktop HD */
|
||||
@media (min-width: 1200px) {}
|
||||
187
project/web/index/new/css/slugify.css
Normal file
187
project/web/index/new/css/slugify.css
Normal file
@@ -0,0 +1,187 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
|
||||
@import url('https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css');
|
||||
*{ margin: 0; padding: 0;}
|
||||
body{
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-smoothing: antialiased;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-size: 15px;
|
||||
background: #eee;
|
||||
}
|
||||
.intro{
|
||||
background: #fff;
|
||||
padding: 60px 30px;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
.intro h1 {
|
||||
font-size: 18pt;
|
||||
padding-bottom: 15px;
|
||||
|
||||
}
|
||||
.intro p{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action{
|
||||
text-align: center;
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
a.btn {
|
||||
text-decoration: none;
|
||||
color: #666;
|
||||
border: 2px solid #666;
|
||||
padding: 10px 15px;
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
}
|
||||
a.btn:hover{
|
||||
background: #666;
|
||||
color: #fff;
|
||||
transition: .3s;
|
||||
-webkit-transition: .3s;
|
||||
}
|
||||
.btn:before{
|
||||
font-family: FontAwesome;
|
||||
font-weight: normal;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.github:before{content: "\f09b"}
|
||||
.down:before{content: "\f019"}
|
||||
.back:before{content:"\f112"}
|
||||
.credit{
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
font-size: 9pt;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-top: 40px;
|
||||
|
||||
}
|
||||
.credit span:before{
|
||||
font-family: FontAwesome;
|
||||
color: #e41b17;
|
||||
content: "\f004";
|
||||
|
||||
|
||||
}
|
||||
.credit a{
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
.credit a:hover{
|
||||
color: #1DBF73;
|
||||
}
|
||||
.credit a:hover:after{
|
||||
font-family: FontAwesome;
|
||||
content: "\f08e";
|
||||
font-size: 9pt;
|
||||
position: absolute;
|
||||
margin: 3px;
|
||||
}
|
||||
main{
|
||||
background: #fff;
|
||||
padding:: 20px;
|
||||
|
||||
}
|
||||
|
||||
article li{
|
||||
color: #444;
|
||||
font-size: 15px;
|
||||
margin-left: 33px;
|
||||
line-height: 1.5;
|
||||
padding: 5px;
|
||||
}
|
||||
article h1,
|
||||
article h2,
|
||||
article h3,
|
||||
article h4,
|
||||
article p{
|
||||
padding: 14px;
|
||||
color: #333;
|
||||
}
|
||||
article p{
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 720px){
|
||||
main{
|
||||
max-width: 720px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.set-overlayer,
|
||||
.set-glass,
|
||||
.set-sticky {
|
||||
cursor: pointer;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
padding: 0 15px;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
.set-overlayer:after,
|
||||
.set-glass:after,
|
||||
.to-active:after,
|
||||
.set-sticky:after {
|
||||
font-family: FontAwesome;
|
||||
font-size: 18pt;
|
||||
position: relative;
|
||||
float: right;
|
||||
}
|
||||
.set-overlayer:after,
|
||||
.set-glass:after,
|
||||
.set-sticky:after {
|
||||
content: "\f204";
|
||||
transition: .6s;
|
||||
}
|
||||
|
||||
.to-active:after {
|
||||
content: "\f205";
|
||||
color: #008080;
|
||||
transition: .6s;
|
||||
}
|
||||
.set-overlayer,
|
||||
.set-glass,
|
||||
.set-sticky,
|
||||
.source,
|
||||
.theme-tray {
|
||||
margin: 10px;
|
||||
background: #f2f2f2;
|
||||
border-radius: 5px;
|
||||
border: 2px solid #f1f1f1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* Syntax Highlighter*/
|
||||
|
||||
pre.prettyprint {
|
||||
padding: 15px !important;
|
||||
margin: 10px;
|
||||
border: 0 !important;
|
||||
background: #f2f2f2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.source {
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
code{
|
||||
border:1px solid #ddd;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
11
project/web/index/new/db/conn.php
Normal file
11
project/web/index/new/db/conn.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__DIR__) . '/bootstrap.php';
|
||||
|
||||
$con = legacy_new_mysqli();
|
||||
|
||||
if ($con->connect_errno) {
|
||||
echo 'Failed to connect to MySQL.';
|
||||
}
|
||||
|
||||
?>
|
||||
269
project/web/index/new/describe.php
Executable file
269
project/web/index/new/describe.php
Executable file
@@ -0,0 +1,269 @@
|
||||
<?php include ('./inc/header.php'); ?>
|
||||
|
||||
<?php
|
||||
ini_set('max_execution_time', 120);
|
||||
set_time_limit(120);
|
||||
|
||||
$producto = "";
|
||||
$ean = "";
|
||||
$autoGenerar = false;
|
||||
$formato = isset($_POST['formato']) ? $_POST['formato'] : 'plano';
|
||||
|
||||
// Si viene por URL
|
||||
if (isset($_GET['name']) && !empty($_GET['name'])) {
|
||||
$producto = urldecode($_GET['name']);
|
||||
$autoGenerar = true;
|
||||
}
|
||||
|
||||
// Si viene por POST
|
||||
if (isset($_POST['prompt'])) {
|
||||
$producto = trim($_POST['prompt']);
|
||||
$autoGenerar = true;
|
||||
}
|
||||
|
||||
// Detectar EAN (8 a 13 dígitos seguidos)
|
||||
if (preg_match('/\b\d{8,13}\b/', $producto, $matches)) {
|
||||
$ean = $matches[0];
|
||||
}
|
||||
|
||||
if ($autoGenerar && !empty($producto)) {
|
||||
|
||||
// Prompt según formato seleccionado
|
||||
if ($formato === 'plano') {
|
||||
$prompt = "
|
||||
Eres un redactor SEO experto en productos naturales, ecológicos y saludables.
|
||||
Genera una descripción en texto plano sin formato ni negritas ni guiones, lista para pegar en CKEditor.
|
||||
|
||||
Sobre el producto: \"$producto\"
|
||||
|
||||
Instrucciones:
|
||||
1. Prioriza la información del fabricante o distribuidor oficial.
|
||||
2. Si el producto tiene un código EAN, utilízalo para obtener información nutricional en OpenFoodFacts.
|
||||
3. No menciones ni enlaces fuentes externas.
|
||||
4. No inventes datos no verificables.
|
||||
|
||||
Estructura del texto:
|
||||
Descripción:
|
||||
Ingredientes:
|
||||
Información nutricional (por 100 g):
|
||||
Beneficios para la salud:
|
||||
Por qué deberías probarlo:
|
||||
Keywords:
|
||||
|
||||
Reglas:
|
||||
- No uses HTML, JSON ni emojis.
|
||||
- No repitas el nombre del producto en exceso.
|
||||
";
|
||||
} else {
|
||||
$prompt = "
|
||||
Eres un redactor SEO especializado en productos naturales y saludables.
|
||||
Genera una descripción optimizada, estructurada y lista para publicación web.
|
||||
|
||||
Producto: \"$producto\"
|
||||
|
||||
Prioriza datos del fabricante o distribuidor oficial, y si el producto tiene un código EAN, usa OpenFoodFacts para complementar la información nutricional.
|
||||
No incluyas ni menciones enlaces externos, ni nombres de tiendas online o marketplaces.
|
||||
|
||||
Estructura del texto:
|
||||
|
||||
### Descripción
|
||||
Breve texto atractivo sobre origen, uso y beneficios.
|
||||
|
||||
### Ingredientes
|
||||
Lista completa y verificada.
|
||||
|
||||
### Información nutricional (por 100 g)
|
||||
Calorías, grasas, hidratos, azúcares, proteínas, fibra y sal. Indica si provienen del fabricante o fuente pública.
|
||||
|
||||
### Beneficios para la salud
|
||||
Texto breve explicando las propiedades y usos.
|
||||
|
||||
### Por qué deberías probarlo
|
||||
Cierre aspiracional, natural y coherente.
|
||||
|
||||
### Keywords
|
||||
Lista de términos relevantes separados por comas.
|
||||
|
||||
Reglas:
|
||||
- No uses HTML ni JSON.
|
||||
- No uses emojis.
|
||||
- No menciones fuentes ni enlaces externos.
|
||||
";
|
||||
}
|
||||
|
||||
$respuesta = obtener_respuesta($prompt);
|
||||
|
||||
// Detectar enlaces válidos (solo OpenFoodFacts o fabricante)
|
||||
$fuentes = [];
|
||||
|
||||
// --- Verificar si el EAN existe realmente en OpenFoodFacts ---
|
||||
if (!empty($ean)) {
|
||||
$url_off = "https://world.openfoodfacts.org/product/$ean";
|
||||
$headers = @get_headers($url_off);
|
||||
if ($headers && strpos($headers[0], '200') !== false) {
|
||||
$fuentes[] = "Información verificada en OpenFoodFacts: $url_off";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Verificar si existe ficha oficial del fabricante (ejemplo: Terpenic Labs) ---
|
||||
$producto_slug = strtolower(str_replace(' ', '-', $producto));
|
||||
$url_fabricante = "https://www.terpenic.com/product-page/" . urlencode($producto_slug);
|
||||
$headers = @get_headers($url_fabricante);
|
||||
if ($headers && strpos($headers[0], '200') !== false) {
|
||||
$fuentes[] = "Ficha oficial del fabricante: $url_fabricante";
|
||||
}
|
||||
|
||||
// --- Agregar los enlaces válidos al final del texto (copiable) ---
|
||||
if (!empty($fuentes)) {
|
||||
$respuesta .= "\n\n" . implode("\n", $fuentes);
|
||||
}
|
||||
}
|
||||
|
||||
function obtener_respuesta($prompt) {
|
||||
$apiKey = trim((string) legacy_config('openai.api_key', ''));
|
||||
$model = legacy_config('openai.model', 'gpt-4o-mini');
|
||||
$endpoint = legacy_config('openai.endpoint', 'https://api.openai.com/v1/chat/completions');
|
||||
|
||||
if ($apiKey === '' || strpos($apiKey, 'CHANGE_ME_') === 0) {
|
||||
return "⚠️ Configura openai.api_key en config/local.php.";
|
||||
}
|
||||
|
||||
$ch = curl_init($endpoint);
|
||||
$data = array(
|
||||
'model' => $model,
|
||||
'messages' => array(
|
||||
array('role' => 'system', 'content' => 'Eres un redactor SEO experto en e-commerce.'),
|
||||
array('role' => 'user', 'content' => $prompt)
|
||||
),
|
||||
'temperature' => 0.6,
|
||||
'max_tokens' => 1200
|
||||
);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $apiKey
|
||||
));
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
|
||||
|
||||
$result = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
return "⚠️ Error de conexión: " . curl_error($ch);
|
||||
}
|
||||
|
||||
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($http_status !== 200) {
|
||||
return "⚠️ Error HTTP $http_status — el servidor no respondió correctamente.";
|
||||
}
|
||||
|
||||
$response = json_decode($result, true);
|
||||
|
||||
if (isset($response['choices'][0]['message']['content'])) {
|
||||
return $response['choices'][0]['message']['content'];
|
||||
} else {
|
||||
return "⚠️ No se pudo generar respuesta. Detalle:\n" . json_encode($response, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
.loader-container {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.loader {
|
||||
border: 5px solid #f3f3f3;
|
||||
border-top: 5px solid #0074D9;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin { 0% { transform: rotate(0deg);} 100% { transform: rotate(360deg);} }
|
||||
.loading-text {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
color: #333;
|
||||
animation: fadeText 1.5s infinite;
|
||||
}
|
||||
@keyframes fadeText {
|
||||
0%,100% {opacity: 0.2;} 50% {opacity: 1;}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<form method="POST" onsubmit="showLoader()">
|
||||
<div class="row" style="margin-top: 20px">
|
||||
<div class="ten columns">
|
||||
<input class="u-full-width" type="text" name="prompt" id="prompt"
|
||||
placeholder="Ingresa el nombre del producto"
|
||||
value="<?php echo htmlspecialchars($producto); ?>">
|
||||
</div>
|
||||
<div class="two columns">
|
||||
<button class="button button-primary u-full-width" type="submit">Generar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top:10px;">
|
||||
<label>
|
||||
<input type="checkbox" name="formato" value="formato" <?php echo ($formato==='formato')?'checked':''; ?>>
|
||||
<span>Con formato para SEO</span>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="loader" class="loader-container">
|
||||
<div class="loader"></div>
|
||||
<div class="loading-text">Generando contenido...</div>
|
||||
</div>
|
||||
|
||||
<?php if ($autoGenerar && !isset($respuesta)) { ?>
|
||||
<script>document.addEventListener("DOMContentLoaded",()=>{document.getElementById("loader").style.display="flex";});</script>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (isset($respuesta)) { ?>
|
||||
<script>document.addEventListener("DOMContentLoaded",()=>{document.getElementById("loader").style.display="none";});</script>
|
||||
|
||||
<div class="row" style="margin-top:20px;">
|
||||
<div class="twelve columns">
|
||||
<pre id="texto-copiar" style="background:#f9f9f9;padding:15px;border-radius:6px;white-space:pre-wrap;font-size:1rem;"><?php echo htmlspecialchars($respuesta); ?></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top:10px;">
|
||||
<div class="two columns">
|
||||
<button class="button button-primary u-full-width" onclick="refreshPage()">
|
||||
<i class="fa fa-refresh fa-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ten columns">
|
||||
<button class="u-full-width" onclick="copiarTexto()">Copiar texto</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showLoader(){document.getElementById("loader").style.display="flex";}
|
||||
function copiarTexto(){
|
||||
var e=document.getElementById("texto-copiar");
|
||||
var t=e.innerText;
|
||||
navigator.clipboard.writeText(t);
|
||||
e.style.backgroundColor="#ffffa0";
|
||||
setTimeout(()=>{e.style.backgroundColor="";alert("Texto copiado al portapapeles");},500);
|
||||
}
|
||||
function refreshPage(){location.reload();}
|
||||
</script>
|
||||
|
||||
<?php include ('./inc/footer.php'); ?>
|
||||
|
||||
BIN
project/web/index/new/images/nopic.png
Normal file
BIN
project/web/index/new/images/nopic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
project/web/index/new/images/rikrdo-white.jpg
Normal file
BIN
project/web/index/new/images/rikrdo-white.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
27
project/web/index/new/inc/atributos.php
Normal file
27
project/web/index/new/inc/atributos.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<fieldset>
|
||||
<?php
|
||||
$atributos = mysqli_query($con," SELECT `oc_attribute_description`.`attribute_id` , `oc_attribute_description`.`language_id` , `oc_attribute_description`.`name` FROM `oc_attribute_description` WHERE `oc_attribute_description`.`language_id` = '4' ORDER BY `oc_attribute_description`.`name`");
|
||||
|
||||
$grupo = 0;
|
||||
$row_count = mysqli_num_rows($atributos);
|
||||
|
||||
//echo $row_count;
|
||||
|
||||
while ($row = mysqli_fetch_assoc($atributos))
|
||||
{
|
||||
if ($grupo == 0) {
|
||||
|
||||
echo '<div class="three columns">';
|
||||
}
|
||||
|
||||
echo '<input type="checkbox" name="atributos[]" value="'. $row['attribute_id'] .'" > ' . ucwords(str_replace("-"," ", $row['name'])) . ' <br>';
|
||||
$grupo++;
|
||||
|
||||
if ($grupo == 4) {
|
||||
|
||||
echo '</div>';
|
||||
$grupo = 0;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</fieldset>
|
||||
184
project/web/index/new/inc/footer.php
Normal file
184
project/web/index/new/inc/footer.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<!-- SCRIPTS JS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<!-- EDITOR CKEDITOR
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<script src="https://code.jquery.com/jquery.min.js"></script>
|
||||
<script src="https://cdn.ckeditor.com/ckeditor5/22.0.0/classic/ckeditor.js"></script>
|
||||
|
||||
<script>
|
||||
ClassicEditor
|
||||
.create( document.querySelector( '#editor' ) )
|
||||
.then( editor => {
|
||||
console.log( editor );
|
||||
} )
|
||||
.catch( error => {
|
||||
console.error( error );
|
||||
} );
|
||||
</script>
|
||||
|
||||
<!-- SEO URL
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<script src="./js/slugify.js"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#slug,#slug-span').slugify('#slug-source');
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- CONVIERTE TEXTO A MAYÚSCULAS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<script>
|
||||
$(".text-uppercase").keyup(function () {
|
||||
this.value = this.value.toLocaleUpperCase();
|
||||
this.value = this.value.replace(/['"]+/g, '');
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- CONVIERTE TEXTO A MINÚSCULAS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<script>
|
||||
$(".text-lowercase").keyup(function () {
|
||||
this.value = this.value.toLocaleLowerCase();
|
||||
this.value = this.value.replace(/['"]+/g, '');
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- AJAX - REFRESCA LISTA DE MARCAS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<script>
|
||||
function refreshBrand()
|
||||
{
|
||||
$.ajax({
|
||||
url: './inc/marcas.php',
|
||||
type: 'post',
|
||||
success: function(data) {
|
||||
$('.newbrand').html(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- VENTANA MODAL CREAR NUEVA MARCA
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<script>
|
||||
// GET THE MODAL
|
||||
var modal = document.getElementById("marcanueva");
|
||||
|
||||
// GET THE BUTTON THAT OPENS THE MODAL
|
||||
var btn = document.getElementById("newbrand");
|
||||
|
||||
// GET THE <SPAN> ELEMENT THAT CLOSES THE MODAL
|
||||
var span = document.getElementsByClassName("close")[0];
|
||||
|
||||
// WHEN THE USER CLICKS THE BUTTON, OPEN THE MODAL
|
||||
btn.onclick = function() {
|
||||
modal.style.display = "block";
|
||||
}
|
||||
|
||||
// WHEN THE USER CLICKS ON <SPAN> (X), CLOSE THE MODAL
|
||||
span.onclick = function() {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
|
||||
// WHEN THE USER CLICKS ANYWHERE OUTSIDE OF THE MODAL, CLOSE IT
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// WHEN THE USER CLICKS THE BUTTON <GUARDAR>, CLOSE THE MODAL
|
||||
function cerrarModal(idModal) {
|
||||
var idModal = idModal;
|
||||
var modala = document.getElementById(idModal);
|
||||
modala.style.display = "none";
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- AJAX - CREAR NUEVA MARCA
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<script>
|
||||
function createBrand()
|
||||
{
|
||||
var nombreMarca= $("#nombreMarca").val();
|
||||
|
||||
$.ajax({
|
||||
url: './inc/newmarca.php',
|
||||
type: 'post',
|
||||
data: "nombreMarca=" + nombreMarca,
|
||||
beforeSend: function(){
|
||||
$('#guardarMarca').hide();
|
||||
$('#loading').show();
|
||||
},
|
||||
complete: function(){
|
||||
$('#loading').hide();
|
||||
$('#guardarMarca').show();
|
||||
cerrarModal('marcanueva');
|
||||
},
|
||||
success: function(data) {
|
||||
refreshBrand();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- ENTER - STOP SUBMITTING FORM
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<script>
|
||||
document.getElementById("newproduct").onkeypress = function(e) {
|
||||
var key = e.charCode || e.keyCode || 0;
|
||||
if (key == 13) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- INPUT 'DATE' PARA SAFARI
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/webshim/1.12.4/extras/modernizr-custom.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/webshim/1.12.4/polyfiller.js"></script>
|
||||
|
||||
<script>
|
||||
webshims.setOptions('waitReady', false);
|
||||
webshims.setOptions('forms-ext', {type: 'date'});
|
||||
webshims.setOptions('forms-ext', {type: 'time'});
|
||||
webshims.polyfill('forms forms-ext');
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function setLink() {
|
||||
var nameValue = document.getElementById("slug-source").value;
|
||||
var brandSelect = document.getElementById("marca");
|
||||
var brandValue = brandSelect.options[brandSelect.selectedIndex].text;
|
||||
var url = "./describe.php?name=" + encodeURIComponent(nameValue) + " " + encodeURIComponent(brandValue);
|
||||
document.getElementById("ia_link").href = url;
|
||||
}
|
||||
|
||||
function validateSelect() {
|
||||
var brandSelect = document.getElementById("marca");
|
||||
var ia_link = document.getElementById("ia_link");
|
||||
if (brandSelect.value) {
|
||||
ia_link.style.display = "block";
|
||||
var element = document.getElementById("url_div");
|
||||
element.className = "ten columns";
|
||||
} else {
|
||||
ia_link.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- FIN SCRIPTS JS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
49
project/web/index/new/inc/header.php
Normal file
49
project/web/index/new/inc/header.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
require_once dirname(__DIR__) . '/bootstrap.php';
|
||||
|
||||
session_start();
|
||||
$_SESSION['after_login'] = $_SERVER['REQUEST_URI'];
|
||||
$_SESSION['acceso'] = TRUE;
|
||||
if (empty($_SESSION['logged'])) {
|
||||
header('Location: ' . legacy_config('routes.login_url', '../login.php'));
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__) . '/db/conn.php';
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
|
||||
<!-- Basic Page Needs
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<meta charset="utf-8">
|
||||
<title>Nuevo Producto</title>
|
||||
<meta name="description" content="Carga rápida de productos - Natural - Mercado de Vida">
|
||||
<meta name="author" content="rikrdo.es">
|
||||
|
||||
<!-- Mobile Specific Metas
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- FONT
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
|
||||
|
||||
<!-- CSS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<link rel="stylesheet" href="./css/normalize.css?<?php echo filemtime('./css/normalize.css') ?>" type="text/css">
|
||||
<link rel="stylesheet" href="./css/skeleton.css?<?php echo filemtime('./css/skeleton.css') ?>" type="text/css">
|
||||
<link rel="stylesheet" href="./css/modal.css?<?php echo filemtime('./css/modal.css') ?>" type="text/css">
|
||||
<link rel="stylesheet" href="./css/custom.css?<?php echo filemtime('./css/custom.css') ?>" type="text/css">
|
||||
|
||||
<!-- Favicon
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<link rel="icon" type="image/png" href="../images/favicon.png">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
12
project/web/index/new/inc/marcas.php
Normal file
12
project/web/index/new/inc/marcas.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php require ('../db/conn.php');?>
|
||||
|
||||
<select name="marca" class="u-full-width" id="marca" required>
|
||||
<option value="" selected disabled>MARCA</option>
|
||||
<?php
|
||||
$marcas = mysqli_query($con," SELECT `oc_manufacturer`.`manufacturer_id` , `oc_manufacturer`.`name` FROM `oc_manufacturer` ORDER BY `oc_manufacturer`.`name`");
|
||||
while ($row = mysqli_fetch_assoc($marcas))
|
||||
{
|
||||
echo '<option value="'. $row['manufacturer_id'] .'" >' . $row['name'] . ' </option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
31
project/web/index/new/inc/newmarca.php
Normal file
31
project/web/index/new/inc/newmarca.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
require ('../db/conn.php');
|
||||
|
||||
$nombreMarca=trim($_POST['nombreMarca']);
|
||||
|
||||
setlocale(LC_ALL, 'en_US.UTF8'); // NECESARIO PARA CONVERTIR CARACTERES ESPECIALES AL ALFABETO INGLÉS
|
||||
|
||||
$urlMarca = htmlspecialchars($nombreMarca);
|
||||
$urlMarca = html_entity_decode($urlMarca);
|
||||
$urlMarca = strip_tags($urlMarca);
|
||||
$urlMarca = preg_replace("/[^a-z0-9 ]/i", " ", iconv('UTF-8', 'ASCII//TRANSLIT', $urlMarca)); // ELIMINA CARACTERES ESPECIALES
|
||||
$urlMarca = preg_replace("/\s+/", " ", $urlMarca); // QUITA DOBLE ESPACIO
|
||||
$urlMarca = trim($urlMarca); // QUITA ESPACIO AL INICIO Y AL FINAL
|
||||
$urlMarca = strtolower(str_replace(" ", "-", $urlMarca)); // REEMPLAZA ESPACIO POR GUION
|
||||
|
||||
$insert_manufacturer = "INSERT INTO `oc_manufacturer` (`manufacturer_id`,`name`, `image`, `sort_order`) VALUES (NULL , '". $nombreMarca ."', '', 0)";
|
||||
|
||||
if (mysqli_query($con, $insert_manufacturer))
|
||||
{
|
||||
$last_id = mysqli_insert_id($con);
|
||||
mysqli_query($con, "INSERT INTO `oc_manufacturer_to_store` (`manufacturer_id`, `store_id`) VALUES (". $last_id .", 0)");
|
||||
mysqli_query($con, "INSERT INTO `oc_url_alias` (`url_alias_id`, `query`, `keyword`)
|
||||
VALUES (NULL , 'manufacturer_id=" . $last_id . "' , '" . $urlMarca . "')");
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<code>Error: " . $insert_manufacturer . "</code><br>" . mysqli_error($con);
|
||||
}
|
||||
|
||||
mysqli_close($con);
|
||||
?>
|
||||
50
project/web/index/new/inc/prompt_en.md
Normal file
50
project/web/index/new/inc/prompt_en.md
Normal file
@@ -0,0 +1,50 @@
|
||||
You are an expert SEO copywriter specialized in natural, organic, and healthy products.
|
||||
Produce an HTML-formatted description that feels authoritative and practical.
|
||||
Use **bold** text and lists when helpful; keep emojis limited to the section labels provided below.
|
||||
|
||||
Product name: "$producto"
|
||||
EAN (if any): "$ean"
|
||||
|
||||
General rules:
|
||||
- Prioritize information from the official manufacturer or distributor.
|
||||
You may consult these sites only as silent references (never mention or link them): nutritienda.com, veritas.es, naturitas.es, iherb.com, dietisur.es, openfoodfacts.org.
|
||||
- Do not invent unverifiable facts. When data is missing, state that it is pending confirmation from the manufacturer.
|
||||
- Only reference external sources if they are the official manufacturer or (when appropriate) OpenFoodFacts.
|
||||
- Avoid `<h1>`, `<h2>`, `<h3>` tags. Stick to `<p>`, `<b>`, `<ul>`, `<li>`, `<h4>`, `<i>`, `<br>`.
|
||||
- Never wrap the answer in Markdown code fences (` ``` `) or any kind of code block; return plain HTML only.
|
||||
- Keep the tone informative, natural, and aligned with *Natural – Mercado de Vida*.
|
||||
- When an EAN is provided, query the OpenFoodFacts API `https://world.openfoodfacts.net/api/v2/product/{EAN}`.
|
||||
* If the response indicates the product is missing or lacks meaningful fields (status ≠ 1 or no name/ingredients/nutrition), do **not** include the OpenFoodFacts link and note that information is pending verification.
|
||||
* Only add the public UI link (`https://world.openfoodfacts.org/product/{EAN}`) when the API returns substantial data for that product.
|
||||
|
||||
Determine the product category before writing:
|
||||
1. **Edible products / beverages / dietary supplements:** include ingredients and nutritional information if available.
|
||||
2. **Topical cosmetics or hygiene items (creams, soaps, etc.):** mention key ingredients or active compounds only when they are typically disclosed; otherwise explain that the composition is pending verification. No nutritional data.
|
||||
3. **Household products (cleaners, detergents, etc.):** focus on functional ingredients or key features; no nutritional information.
|
||||
4. **Accessories, utensils, equipment, grooming tools (e.g., combs, bottles, blenders):** skip ingredients and nutritional information entirely. Highlight materials, design details, usage tips, and care instructions.
|
||||
5. If the product type does not fit any of the above, use judgment and omit sections that are clearly irrelevant.
|
||||
|
||||
Section guidelines (adapt as needed based on the category analysis):
|
||||
|
||||
🪴 **Description:**
|
||||
Concise overview of purpose, origin, and main benefits. Always include this section.
|
||||
|
||||
🌿 **Ingredients / Key components:**
|
||||
- Include only when the product category reasonably has a composition list (foods, supplements, cosmetics, household consumables).
|
||||
- If data is missing, note that it is pending verification.
|
||||
- For accessories or tools, replace this section with **Key Features** describing materials or build qualities.
|
||||
|
||||
🍎 **Nutritional information (per 100 g / per serving):**
|
||||
- Provide full table (calories, fats, carbohydrates, sugars, proteins, fiber, salt) only for edible items or supplements when data exists.
|
||||
- If unavailable, state it is pending verification.
|
||||
- Omit this section entirely for non-ingestible products.
|
||||
|
||||
💚 **Health benefits / Functional benefits:**
|
||||
Explain how the product supports wellbeing, personal care, or practical use. Adjust wording to fit the category.
|
||||
|
||||
✨ **Why you should try it / Usage tips / Care instructions:**
|
||||
Close with an aspirational or practical paragraph encouraging its use, tailored to the product type.
|
||||
|
||||
OpenFoodFacts link:
|
||||
If the item is edible or a supplement **and** the EAN is available, add a final sentence linking to the corresponding OpenFoodFacts page. Do not add this link for non-food items.
|
||||
|
||||
49
project/web/index/new/inc/prompt_es.md
Normal file
49
project/web/index/new/inc/prompt_es.md
Normal file
@@ -0,0 +1,49 @@
|
||||
Eres un redactor SEO experto en productos naturales, ecológicos y saludables.
|
||||
Genera una descripción en HTML que resulte rigurosa y útil para la tienda.
|
||||
|
||||
Nombre del producto: "$producto"
|
||||
EAN (si existe): "$ean"
|
||||
|
||||
Reglas generales:
|
||||
- Prioriza la información del fabricante o distribuidor oficial.
|
||||
Puedes consultar estas webs solo como referencia interna (no las cites ni enlaces): nutritienda.com, veritas.es, naturitas.es, iherb.com, dietisur.es, openfoodfacts.org.
|
||||
- No inventes datos que no puedas respaldar. Si la información falta, indícalo como pendiente de verificación en la web del fabricante.
|
||||
- Solo menciona fuentes externas si son el fabricante u OpenFoodFacts cuando corresponda.
|
||||
- Emplea únicamente etiquetas HTML sencillas: `<p>`, `<b>`, `<ul>`, `<li>`, `<h4>`, `<i>`, `<br>`. Evita `<h1>`, `<h2>`, `<h3>`.
|
||||
- No devuelvas el contenido dentro de bloques de código ni fences Markdown (` ``` `); responde únicamente con HTML plano.
|
||||
- Estilo profesional, cercano y acorde a *Natural – Mercado de Vida*.
|
||||
- Cuando exista EAN, consulta la API `https://world.openfoodfacts.net/api/v2/product/{EAN}`.
|
||||
* Si la respuesta indica que el producto no existe o no aporta datos relevantes (status ≠ 1 o sin nombre/ingredientes/nutrición), no añadas el enlace a OpenFoodFacts e indica que la información está pendiente.
|
||||
* Solo agrega el enlace público (`https://world.openfoodfacts.org/product/{EAN}`) cuando la API devuelva información sustancial del producto.
|
||||
|
||||
Antes de redactar, identifica el tipo de producto:
|
||||
1. **Alimentos, bebidas o complementos alimenticios:** incluye ingredientes y tabla nutricional si están disponibles.
|
||||
2. **Cosmética o higiene (cremas, champús, jabones, etc.):** menciona ingredientes o activos principales solo si suelen publicarse; de lo contrario, explica que la composición está pendiente de verificación. No aportes información nutricional.
|
||||
3. **Limpieza del hogar y detergentes:** describe ingredientes funcionales o características clave; no incluyas datos nutricionales.
|
||||
4. **Accesorios, utensilios, equipamiento, herramientas de cuidado personal (ej. peines, botellas, batidoras):** omite ingredientes e información nutricional. En su lugar, destaca materiales, diseño, uso y mantenimiento.
|
||||
5. Si el producto no encaja en las categorías anteriores, aplica criterio y elimina las secciones que no sean pertinentes.
|
||||
|
||||
Guía de secciones (adáptalas según lo anterior):
|
||||
|
||||
🪴 **Descripción:**
|
||||
Resumen breve sobre finalidad, origen y beneficios principales. Siempre debe aparecer.
|
||||
|
||||
🌿 **Ingredientes / Componentes clave / Características:**
|
||||
- Usa “Ingredientes” solo cuando el producto realmente tenga una lista de composición.
|
||||
- Para accesorios o herramientas, cambia el título por “Características principales” y describe materiales, tecnología o acabados.
|
||||
- Si no hay datos confirmados, indica que están pendientes de verificación.
|
||||
|
||||
🍎 **Información nutricional (por 100 g o por dosis):**
|
||||
- Solo para alimentos o complementos cuando haya datos.
|
||||
- Si la información no está disponible, menciona que falta confirmación.
|
||||
- Elimina por completo esta sección cuando el producto no sea ingerible.
|
||||
|
||||
💚 **Beneficios para la salud / Beneficios funcionales:**
|
||||
Explica cómo contribuye al bienestar, cuidado personal o utilidad práctica según el caso.
|
||||
|
||||
✨ **Por qué deberías probarlo / Consejos de uso / Mantenimiento:**
|
||||
Cierre inspirador o práctico que anime a utilizarlo, adaptado al tipo de producto.
|
||||
|
||||
Enlace a OpenFoodFacts:
|
||||
Si es un producto comestible o suplemento y el EAN está disponible, añade al final una frase con enlace a su ficha en OpenFoodFacts. No incluyas este enlace para artículos no alimentarios.
|
||||
|
||||
190
project/web/index/new/index.php
Normal file
190
project/web/index/new/index.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php include ('./inc/header.php'); ?>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- FORMULARIO
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
|
||||
<form enctype="multipart/form-data" action="<?php echo htmlspecialchars(legacy_config('routes.success_url', '')); ?>" name = "newproduct" id = "newproduct" method = "POST">
|
||||
|
||||
<input name="pwd" type="hidden" value="<?php echo htmlspecialchars(legacy_config('security.form_password_hash', '')); ?>">
|
||||
|
||||
<!-- CODIGO, SEO URL
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row" style = "margin-top: 20px">
|
||||
<div class="ten columns" id = "url_div">
|
||||
<span class="slug-ouput"> <b><input type ="text" name="url" value="" placeholder="SEO URL" class="u-full-width" id="slug" tabindex="-1" /></b></span>
|
||||
</div>
|
||||
<div class="two columns">
|
||||
<a id="ia_link" href="" class="button button-primary u-full-width" target="_blank"><i class="fa fa-lightbulb-o fa-lg" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NOMBRE DEL PRODUCTO
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row" >
|
||||
<div class="nine columns">
|
||||
<label for="slug-source">Nombre del Artículo: </label>
|
||||
<input type ="text" name="nombre" value="" class="u-full-width text-uppercase" id="slug-source" oninput="setLink()" required />
|
||||
</div>
|
||||
<div class="three columns">
|
||||
<label for="slug-source">EAN: </label>
|
||||
<input type ="text" name="ean" value="" class="u-full-width" id="ean" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CATEGORIAS, MARCA
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row">
|
||||
<div class="six columns">
|
||||
<select name="categoria" class="u-full-width" id="categoria" required >
|
||||
<option value="" selected disabled>CATEGORÍA</option>
|
||||
<?php
|
||||
$categorias = mysqli_query($con," SELECT * FROM `oc_category`, `oc_category_description`, `oc_category_to_store` WHERE `oc_category`.`category_id` = `oc_category_description`.`category_id` AND `oc_category_description`.`category_id`= `oc_category_to_store`.`category_id` AND `oc_category_description`.`language_id` = 4 ORDER BY `oc_category_description`.`name` ASC");
|
||||
while ($row = mysqli_fetch_assoc($categorias))
|
||||
{
|
||||
echo '<option value="'. $row['category_id'] .'" >' . $row['name'] . ' </option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="four columns newbrand">
|
||||
<select name="marca" class="u-full-width" id="marca" onchange="validateSelect(),setLink()" required>
|
||||
<option value="" selected disabled>MARCA</option>
|
||||
<?php
|
||||
$marcas = mysqli_query($con," SELECT `oc_manufacturer`.`manufacturer_id` , `oc_manufacturer`.`name` FROM `oc_manufacturer` ORDER BY `oc_manufacturer`.`name`");
|
||||
while ($row = mysqli_fetch_assoc($marcas))
|
||||
{
|
||||
echo '<option value="'. $row['manufacturer_id'] .'" >' . $row['name'] . ' </option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="two columns">
|
||||
<!-- Abre Ventana Modal -->
|
||||
<span class="button button-primary u-full-width" id="newbrand"><i class="fa fa-pencil fa-lg" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PVP, COSTE, IVA
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row">
|
||||
<div class="four columns">
|
||||
<label for="pvp">PVP: </label>
|
||||
<input type ="number" name="pvp" value="" class="u-full-width" id="pvp" step="any" required />
|
||||
</div>
|
||||
<div class="four columns">
|
||||
<label for="coste">Coste: </label>
|
||||
<input type ="number" name="coste" value="" class="u-full-width" id="coste" step="any" required />
|
||||
</div>
|
||||
<div class="four columns">
|
||||
<label for="iva">IVA: </label>
|
||||
<select name="iva" class="u-full-width" id="iva" required >
|
||||
<?php
|
||||
$iva = mysqli_query($con," SELECT `tax_class_id` , `title` FROM `oc_tax_class` ORDER BY `title` ASC");
|
||||
while ($row = mysqli_fetch_assoc($iva))
|
||||
{
|
||||
echo '<option value="'. $row['tax_class_id'] .'" >' . substr($row['title'], 4) . ' </option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CANTIDAD, CADUCIDAD, PESO
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row">
|
||||
<div class="four columns">
|
||||
<label for="cantidad">Cantidad: </label>
|
||||
<input type ="number" name="cantidad" value="" class="u-full-width" id="cantidad" required>
|
||||
</div>
|
||||
<div class="four columns">
|
||||
<label for="caducidad">Caducidad: </label>
|
||||
<input type ="date" name="caducidad" value="" min="<?php echo date('Y-m-d');?>" class="u-full-width" id="caducidad" required />
|
||||
</div>
|
||||
<div class="four columns">
|
||||
<label for="peso">Peso: </label>
|
||||
<select name="peso" class="u-full-width" id="peso" required>
|
||||
<option value="0.100">100gr</option>
|
||||
<option value="0.250" selected>250gr</option>
|
||||
<option value="0.500">500gr</option>
|
||||
<option value="0.750">750gr</option>
|
||||
<option value="1.000">1kg</option>
|
||||
<option value="1.500">1,5kg</option>
|
||||
<option value="2.000">2kg</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FIELDSET ATRIBUTOS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row" id="atrib_zone">
|
||||
<div class="twelve columns">
|
||||
<?php include ('inc/atributos.php'); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EDITOR JS
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row" style = "margin-top: 20px">
|
||||
<div class="twelve columns">
|
||||
<textarea name="editor" id="editor" placeholder="Descripcion del producto..." class="u-full-width"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CARGAR IMAGEN X URL
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row" style = "margin-top: 20px">
|
||||
<div class="twelve columns">
|
||||
<input type ="text" name="image_path" id="image_path" class="u-full-width" placeholder="Pegar URL de la Imagen" onchange="document.getElementById('imgElement').src = $('#image_path').val()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UPLOAD IMAGEN LOCAL
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div class="row" style = "margin-top: 20px">
|
||||
<div class="four columns" id="imgPreview">
|
||||
<img id="imgElement" alt="Imagen de Producto" src="./images/nopic.png" />
|
||||
</div>
|
||||
<div class="four columns" >
|
||||
<p class="infoText">* Formatos soportados JPG y PNG</p>
|
||||
<label for="imagen" class="uploadImg">
|
||||
<i class="fa fa-cloud-upload"> </i> Cargar Imágen
|
||||
</label>
|
||||
<input type="file" class="u-full-width" name="imagen" id="imagen" onchange="document.getElementById('imgElement').src = window.URL.createObjectURL(this.files[0])">
|
||||
</div>
|
||||
<div class="four columns" >
|
||||
<button type="submit" class="button-primary u-full-width" name="submit" id="crearProducto" >Guardar Producto</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- VENTANA MODAL 'NUEVA MARCA'
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– -->
|
||||
<div id="marcanueva" class="modal">
|
||||
<!-- CONTENIDO VENTANA MODAL -->
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<form enctype="multipart/form-data" action="inc/newmarca.php" method = "POST">
|
||||
<div class="row" >
|
||||
<div class="twelve columns">
|
||||
<input type ="text" name="nombreMarca" value="" class="u-full-width text-uppercase" id="nombreMarca" onfocus="this.value=''" placeholder="Nombre de la Marca" required />
|
||||
</div>
|
||||
<div class="modalfoot" >
|
||||
<button onclick="createBrand();return false" class="button button-primary u-full-width"id="guardarMarca" >Guardar</button>
|
||||
<button class="button button-primary u-full-width" id="loading" disabled><i class="fa fa-spinner fa-spin"></i> Guardando...</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php
|
||||
mysqli_close($con);
|
||||
include ('./inc/footer.php');
|
||||
?>
|
||||
613
project/web/index/new/js/slugify.js
Normal file
613
project/web/index/new/js/slugify.js
Normal file
@@ -0,0 +1,613 @@
|
||||
/*! Slugify - v0.1.0 - 2013-05-22
|
||||
* https://github.com/madflow/jquery-slugify
|
||||
* Copyright (c) 2013 madflow; Licensed MIT */
|
||||
;(function($) {
|
||||
|
||||
$.fn.slugify = function(source, options) {
|
||||
return this.each(function() {
|
||||
var $target = $(this),
|
||||
$source = $(source);
|
||||
|
||||
$target.on('keyup change',function() {
|
||||
if($target.val() !== '' && $target.val() !== undefined) {
|
||||
$target.data('locked', true);
|
||||
} else {
|
||||
$target.data('locked', false);
|
||||
}
|
||||
});
|
||||
|
||||
$source.on('keyup change',function() {
|
||||
if( true === $target.data('locked')) {return;}
|
||||
if($target.is('input') || $target.is('textarea')) {
|
||||
$target.val($.slugify($source.val(), options));
|
||||
} else {
|
||||
$target.text($.slugify($source.val(), options));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Static method.
|
||||
$.slugify = function(sourceString, options) {
|
||||
// Override default options with passed-in options.
|
||||
options = $.extend({}, $.slugify.options, options);
|
||||
sourceString = $.trim(sourceString); // Trim
|
||||
sourceString = sourceString.toLowerCase(); // Lower Case
|
||||
$.each(options.replaceMap, function(key, value) { // Special char map
|
||||
sourceString = sourceString.replace(new RegExp(key, 'g'), value || options.invalid);
|
||||
});
|
||||
return sourceString
|
||||
.replace(/\s+/g, options.whitespace) // Replace whitespace characters
|
||||
.replace(new RegExp('[^a-z0-9 '+ options.whitespace +']', 'g'), options.invalid); // Replace invalid characters
|
||||
};
|
||||
|
||||
// Default options
|
||||
$.slugify.options = {
|
||||
whitespace: '-',
|
||||
invalid: '',
|
||||
replaceMap: {
|
||||
'á': 'a',
|
||||
'Ã ': 'a',
|
||||
'â': 'a',
|
||||
'ä': 'ae',
|
||||
'ã': 'a',
|
||||
'æ': 'ae',
|
||||
'ç': 'c',
|
||||
'é': 'e',
|
||||
'è': 'e',
|
||||
'ê': 'e',
|
||||
'ë': 'e',
|
||||
'ẽ': 'e',
|
||||
'Ã': 'i',
|
||||
'ì': 'i',
|
||||
'î': 'i',
|
||||
'ï': 'i',
|
||||
'Ä©': 'i',
|
||||
'ó': 'o',
|
||||
'ò': 'o',
|
||||
'ô': 'o',
|
||||
'ö': 'oe',
|
||||
'õ': 'o',
|
||||
'Å“': 'oe',
|
||||
'ß': 'ss',
|
||||
'ú': 'u',
|
||||
'ù': 'u',
|
||||
'û': 'u',
|
||||
'ü': 'ue',
|
||||
'Å©': 'u',
|
||||
'ă': 'a',
|
||||
'ắ': 'a',
|
||||
'ằ': 'a',
|
||||
'ẵ': 'a',
|
||||
'ẳ': 'a',
|
||||
'ấ': 'a',
|
||||
'ầ': 'a',
|
||||
'ẫ': 'a',
|
||||
'ẩ': 'a',
|
||||
'ÇŽ': 'a',
|
||||
'Ã¥': 'a',
|
||||
'Ç»': 'a',
|
||||
'ÇŸ': 'a',
|
||||
'ȧ': 'a',
|
||||
'Ç¡': 'a',
|
||||
'Ä…': 'a',
|
||||
'Ä': 'a',
|
||||
'ả': 'a',
|
||||
'È': 'a',
|
||||
'ȃ': 'a',
|
||||
'ạ': 'a',
|
||||
'ặ': 'a',
|
||||
'áº': 'a',
|
||||
'á¸': 'a',
|
||||
'â±¥': 'a',
|
||||
'á¶': 'a',
|
||||
'É': 'a',
|
||||
'É‘': 'a',
|
||||
'ḃ': 'b',
|
||||
'ḅ': 'b',
|
||||
'ḇ': 'b',
|
||||
'Æ€': 'b',
|
||||
'É“': 'b',
|
||||
'ƃ': 'b',
|
||||
'ᵬ': 'b',
|
||||
'á¶€': 'b',
|
||||
'þ': 'b',
|
||||
'ć': 'c',
|
||||
'ĉ': 'c',
|
||||
'Ä': 'c',
|
||||
'Ä‹': 'c',
|
||||
'ḉ': 'c',
|
||||
'ȼ': 'c',
|
||||
'ƈ': 'c',
|
||||
'É•': 'c',
|
||||
'Ä': 'd',
|
||||
'ḋ': 'd',
|
||||
'ḑ': 'd',
|
||||
'á¸': 'd',
|
||||
'ḓ': 'd',
|
||||
'á¸': 'd',
|
||||
'Ä‘': 'd',
|
||||
'É–': 'd',
|
||||
'É—': 'd',
|
||||
'ƌ': 'd',
|
||||
'áµ': 'd',
|
||||
'á¶': 'd',
|
||||
'á¶‘': 'd',
|
||||
'È¡': 'd',
|
||||
'∂': 'd',
|
||||
'Ä•': 'e',
|
||||
'ế': 'e',
|
||||
'á»': 'e',
|
||||
'á»…': 'e',
|
||||
'ể': 'e',
|
||||
'Ä›': 'e',
|
||||
'Ä—': 'e',
|
||||
'È©': 'e',
|
||||
'á¸': 'e',
|
||||
'Ä™': 'e',
|
||||
'Ä“': 'e',
|
||||
'ḗ': 'e',
|
||||
'ḕ': 'e',
|
||||
'ẻ': 'e',
|
||||
'È…': 'e',
|
||||
'ȇ': 'e',
|
||||
'ẹ': 'e',
|
||||
'ệ': 'e',
|
||||
'ḙ': 'e',
|
||||
'ḛ': 'e',
|
||||
'ɇ': 'e',
|
||||
'á¶’': 'e',
|
||||
'ḟ': 'f',
|
||||
'Æ’': 'f',
|
||||
'áµ®': 'f',
|
||||
'á¶‚': 'f',
|
||||
'ǵ': 'g',
|
||||
'ÄŸ': 'g',
|
||||
'Ä': 'g',
|
||||
'ǧ': 'g',
|
||||
'Ä¡': 'g',
|
||||
'Ä£': 'g',
|
||||
'ḡ': 'g',
|
||||
'Ç¥': 'g',
|
||||
'É ': 'g',
|
||||
'ᶃ': 'g',
|
||||
'Ä¥': 'h',
|
||||
'ÈŸ': 'h',
|
||||
'ḧ': 'h',
|
||||
'ḣ': 'h',
|
||||
'ḩ': 'h',
|
||||
'ḥ': 'h',
|
||||
'ḫ': 'h',
|
||||
'ẖ': 'h',
|
||||
'ħ': 'h',
|
||||
'ⱨ': 'h',
|
||||
'Ä': 'i',
|
||||
'Ç': 'i',
|
||||
'ḯ': 'i',
|
||||
'į': 'i',
|
||||
'Ä«': 'i',
|
||||
'ỉ': 'i',
|
||||
'ȉ': 'i',
|
||||
'È‹': 'i',
|
||||
'ị': 'i',
|
||||
'á¸': 'i',
|
||||
'ɨ': 'i',
|
||||
'áµ»': 'i',
|
||||
'á¶–': 'i',
|
||||
'i': 'i',
|
||||
'ı': 'i',
|
||||
'ĵ': 'j',
|
||||
'ɉ': 'j',
|
||||
'ǰ': 'j',
|
||||
'È·': 'j',
|
||||
'Ê': 'j',
|
||||
'ÉŸ': 'j',
|
||||
'Ê„': 'j',
|
||||
'ḱ': 'k',
|
||||
'Ç©': 'k',
|
||||
'Ä·': 'k',
|
||||
'ḳ': 'k',
|
||||
'ḵ': 'k',
|
||||
'Æ™': 'k',
|
||||
'ⱪ': 'k',
|
||||
'á¶„': 'k',
|
||||
'ĺ': 'l',
|
||||
'ľ': 'l',
|
||||
'ļ': 'l',
|
||||
'ḷ': 'l',
|
||||
'ḹ': 'l',
|
||||
'ḽ': 'l',
|
||||
'ḻ': 'l',
|
||||
'Å‚': 'l',
|
||||
'Å€': 'l',
|
||||
'Æš': 'l',
|
||||
'ⱡ': 'l',
|
||||
'É«': 'l',
|
||||
'ɬ': 'l',
|
||||
'á¶…': 'l',
|
||||
'É': 'l',
|
||||
'È´': 'l',
|
||||
'ḿ': 'm',
|
||||
'á¹': 'm',
|
||||
'ṃ': 'm',
|
||||
'ᵯ': 'm',
|
||||
'ᶆ': 'm',
|
||||
'ɱ': 'm',
|
||||
|
||||
'Å„': 'n',
|
||||
'ǹ': 'n',
|
||||
'ň': 'n',
|
||||
'ñ': 'n',
|
||||
'á¹…': 'n',
|
||||
'ņ': 'n',
|
||||
'ṇ': 'n',
|
||||
'ṋ': 'n',
|
||||
'ṉ': 'n',
|
||||
'n̈': 'n',
|
||||
'ɲ': 'n',
|
||||
'Æž': 'n',
|
||||
'Å‹': 'n',
|
||||
'áµ°': 'n',
|
||||
'ᶇ': 'n',
|
||||
'ɳ': 'n',
|
||||
'ȵ': 'n',
|
||||
'Å': 'o',
|
||||
'ố': 'o',
|
||||
'ồ': 'o',
|
||||
'á»—': 'o',
|
||||
'ổ': 'o',
|
||||
'Ç’': 'o',
|
||||
'È«': 'o',
|
||||
'Å‘': 'o',
|
||||
'á¹': 'o',
|
||||
'á¹': 'o',
|
||||
'È': 'o',
|
||||
'ȯ': 'o',
|
||||
'͘o͘': 'o',
|
||||
'ȱ': 'o',
|
||||
'ø': 'o',
|
||||
'Ç¿': 'o',
|
||||
'Ç«': 'o',
|
||||
'Ç': 'o',
|
||||
'Å': 'o',
|
||||
'ṓ': 'o',
|
||||
'ṑ': 'o',
|
||||
'á»': 'o',
|
||||
'È': 'o',
|
||||
'È': 'o',
|
||||
'Æ¡': 'o',
|
||||
'á»›': 'o',
|
||||
'á»': 'o',
|
||||
'ỡ': 'o',
|
||||
'ở': 'o',
|
||||
'ợ': 'o',
|
||||
'á»': 'o',
|
||||
'á»™': 'o',
|
||||
'ɵ': 'o',
|
||||
'É”': 'o',
|
||||
'ṕ': 'p',
|
||||
'á¹—': 'p',
|
||||
'áµ½': 'p',
|
||||
'Æ¥': 'p',
|
||||
'p̃': 'p',
|
||||
'áµ±': 'p',
|
||||
'ᶈ': 'p',
|
||||
'É‹': 'q',
|
||||
'Æ£': 'q',
|
||||
'Ê ': 'q',
|
||||
'Å•': 'r',
|
||||
'Å™': 'r',
|
||||
'á¹™': 'r',
|
||||
'Å—': 'r',
|
||||
'È‘': 'r',
|
||||
'È“': 'r',
|
||||
'á¹›': 'r',
|
||||
'á¹': 'r',
|
||||
'ṟ': 'r',
|
||||
'É': 'r',
|
||||
'ɽ': 'r',
|
||||
'áµ²': 'r',
|
||||
'ᶉ': 'r',
|
||||
'ɼ': 'r',
|
||||
'ɾ': 'r',
|
||||
'áµ³': 'r',
|
||||
'Å›': 's',
|
||||
'á¹¥': 's',
|
||||
'Å': 's',
|
||||
'Å¡': 's',
|
||||
'á¹§': 's',
|
||||
'ṡẛ': 's',
|
||||
'ÅŸ': 's',
|
||||
'á¹£': 's',
|
||||
'ṩ': 's',
|
||||
'È™': 's',
|
||||
's̩': 's',
|
||||
'áµ´': 's',
|
||||
'á¶Š': 's',
|
||||
'Ê‚': 's',
|
||||
'È¿': 's',
|
||||
'Å¥': 't',
|
||||
'ṫ': 't',
|
||||
'Å£': 't',
|
||||
'á¹': 't',
|
||||
'È›': 't',
|
||||
'á¹±': 't',
|
||||
'ṯ': 't',
|
||||
'ŧ': 't',
|
||||
'ⱦ': 't',
|
||||
'Æ': 't',
|
||||
'ʈ': 't',
|
||||
'̈ẗ': 't',
|
||||
'áµµ': 't',
|
||||
'Æ«': 't',
|
||||
'ȶ': 't',
|
||||
'Å': 'u',
|
||||
'Ç”': 'u',
|
||||
'ů': 'u',
|
||||
'ǘ': 'u',
|
||||
'ǜ': 'u',
|
||||
'Çš': 'u',
|
||||
'Ç–': 'u',
|
||||
'ű': 'u',
|
||||
'á¹¹': 'u',
|
||||
'ų': 'u',
|
||||
'Å«': 'u',
|
||||
'á¹»': 'u',
|
||||
'á»§': 'u',
|
||||
'È•': 'u',
|
||||
'È—': 'u',
|
||||
'ư': 'u',
|
||||
'ứ': 'u',
|
||||
'ừ': 'u',
|
||||
'ữ': 'u',
|
||||
'á»': 'u',
|
||||
'á»±': 'u',
|
||||
'ụ': 'u',
|
||||
'á¹³': 'u',
|
||||
'á¹·': 'u',
|
||||
'á¹µ': 'u',
|
||||
'ʉ': 'u',
|
||||
'áµ¾': 'u',
|
||||
'á¶™': 'u',
|
||||
'á¹½': 'v',
|
||||
'ṿ': 'v',
|
||||
'Ê‹': 'v',
|
||||
'ᶌ': 'v',
|
||||
'â±´': 'v',
|
||||
'ẃ': 'w',
|
||||
'áº': 'w',
|
||||
'ŵ': 'w',
|
||||
'ẅ': 'w',
|
||||
'ẇ': 'w',
|
||||
'ẉ': 'w',
|
||||
'ẘ': 'w',
|
||||
'áº': 'x',
|
||||
'ẋ': 'x',
|
||||
'á¶': 'x',
|
||||
'ý': 'y',
|
||||
'ỳ': 'y',
|
||||
'Å·': 'y',
|
||||
'ẙ': 'y',
|
||||
'ÿ': 'y',
|
||||
'ỹ': 'y',
|
||||
'áº': 'y',
|
||||
'ȳ': 'y',
|
||||
'á»·': 'y',
|
||||
'ỵ': 'y',
|
||||
'É': 'y',
|
||||
'Æ´': 'y',
|
||||
'Ê': 'y',
|
||||
'ź': 'z',
|
||||
'ẑ': 'z',
|
||||
'ž': 'z',
|
||||
'ż': 'z',
|
||||
'ẓ': 'z',
|
||||
'ẕ': 'z',
|
||||
'ƶ': 'z',
|
||||
'È¥': 'z',
|
||||
'ⱬ': 'z',
|
||||
'áµ¶': 'z',
|
||||
'á¶Ž': 'z',
|
||||
'Ê': 'z',
|
||||
'Ê‘': 'z',
|
||||
'É€': 'z',
|
||||
'α': 'a',
|
||||
'β': 'b',
|
||||
'γ': 'g',
|
||||
'É£': 'g',
|
||||
'δ': 'd',
|
||||
'ð': 'd',
|
||||
'ε': 'e',
|
||||
'ζ': 'z',
|
||||
'η': 'i',
|
||||
'θ': 'th',
|
||||
'ι': 'i',
|
||||
'κ': 'k',
|
||||
'λ': 'l',
|
||||
'μ': 'm',
|
||||
'µ': 'm',
|
||||
'ν': 'n',
|
||||
'ξ': 'x',
|
||||
'ο': 'o',
|
||||
'Ï€': 'p',
|
||||
'Ï': 'r',
|
||||
'σ': 's',
|
||||
'Ï‚': 's',
|
||||
'Ï„': 't',
|
||||
'Ï…': 'u',
|
||||
'φ': 'f',
|
||||
'χ': 'ch',
|
||||
'ψ': 'ps',
|
||||
'ω': 'o',
|
||||
'á¾³': 'a',
|
||||
'ά': 'a',
|
||||
'á½°': 'a',
|
||||
'á¾´': 'a',
|
||||
'á¾²': 'a',
|
||||
'á¾¶': 'a',
|
||||
'á¾·': 'a',
|
||||
'á¼€': 'a',
|
||||
'á¾€': 'a',
|
||||
'ἄ': 'a',
|
||||
'ᾄ': 'a',
|
||||
'ἂ': 'a',
|
||||
'ᾂ': 'a',
|
||||
'ἆ': 'a',
|
||||
'ᾆ': 'a',
|
||||
'á¼': 'a',
|
||||
'á¾': 'a',
|
||||
'á¼…': 'a',
|
||||
'á¾…': 'a',
|
||||
'ἃ': 'a',
|
||||
'ᾃ': 'a',
|
||||
'ἇ': 'a',
|
||||
'ᾇ': 'a',
|
||||
'á¾±': 'a',
|
||||
'á¾°': 'a',
|
||||
'Î': 'e',
|
||||
'á½²': 'e',
|
||||
'á¼': 'e',
|
||||
'á¼”': 'e',
|
||||
'á¼’': 'e',
|
||||
'ἑ': 'e',
|
||||
'ἕ': 'e',
|
||||
'ἓ': 'e',
|
||||
'ῃ': 'i',
|
||||
'ή': 'i',
|
||||
'á½´': 'i',
|
||||
'á¿„': 'i',
|
||||
'á¿‚': 'i',
|
||||
'ῆ': 'i',
|
||||
'ῇ': 'i',
|
||||
'á¼ ': 'i',
|
||||
'á¾': 'i',
|
||||
'ἤ': 'i',
|
||||
'á¾”': 'i',
|
||||
'á¼¢': 'i',
|
||||
'á¾’': 'i',
|
||||
'ἦ': 'i',
|
||||
'á¾–': 'i',
|
||||
'ἡ': 'i',
|
||||
'ᾑ': 'i',
|
||||
'á¼¥': 'i',
|
||||
'ᾕ': 'i',
|
||||
'á¼£': 'i',
|
||||
'ᾓ': 'i',
|
||||
'á¼§': 'i',
|
||||
'á¾—': 'i',
|
||||
'ί': 'i',
|
||||
'á½¶': 'i',
|
||||
'á¿–': 'i',
|
||||
'á¼°': 'i',
|
||||
'á¼´': 'i',
|
||||
'á¼²': 'i',
|
||||
'á¼¶': 'i',
|
||||
'á¼±': 'i',
|
||||
'á¼µ': 'i',
|
||||
'á¼³': 'i',
|
||||
'á¼·': 'i',
|
||||
'ÏŠ': 'i',
|
||||
'Î': 'i',
|
||||
'á¿’': 'i',
|
||||
'á¿—': 'i',
|
||||
'á¿‘': 'i',
|
||||
'á¿': 'i',
|
||||
'ό': 'o',
|
||||
'ὸ': 'o',
|
||||
'á½€': 'o',
|
||||
'ὄ': 'o',
|
||||
'ὂ': 'o',
|
||||
'á½': 'o',
|
||||
'á½…': 'o',
|
||||
'ὃ': 'o',
|
||||
'Ï': 'u',
|
||||
'ὺ': 'u',
|
||||
'ῦ': 'u',
|
||||
'á½': 'u',
|
||||
'á½”': 'u',
|
||||
'á½’': 'u',
|
||||
'á½–': 'u',
|
||||
'ὑ': 'u',
|
||||
'ὕ': 'u',
|
||||
'ὓ': 'u',
|
||||
'á½—': 'u',
|
||||
'Ï‹': 'u',
|
||||
'ΰ': 'u',
|
||||
'á¿¢': 'u',
|
||||
'á¿§': 'u',
|
||||
'á¿¡': 'u',
|
||||
'á¿ ': 'u',
|
||||
'ῳ': 'o',
|
||||
'ÏŽ': 'o',
|
||||
'á¿´': 'o',
|
||||
'á½¼': 'o',
|
||||
'ῲ': 'o',
|
||||
'á¿¶': 'o',
|
||||
'á¿·': 'o',
|
||||
'á½ ': 'o',
|
||||
'á¾ ': 'o',
|
||||
'ὤ': 'o',
|
||||
'ᾤ': 'o',
|
||||
'á½¢': 'o',
|
||||
'á¾¢': 'o',
|
||||
'ὦ': 'o',
|
||||
'ᾦ': 'o',
|
||||
'ὡ': 'o',
|
||||
'ᾡ': 'o',
|
||||
'á½¥': 'o',
|
||||
'á¾¥': 'o',
|
||||
'á½£': 'o',
|
||||
'á¾£': 'o',
|
||||
'á½§': 'o',
|
||||
'á¾§': 'o',
|
||||
'ῤ': 'r',
|
||||
'á¿¥': 'r',
|
||||
'а': 'a',
|
||||
'б': 'b',
|
||||
'в': 'v',
|
||||
'г': 'g',
|
||||
'д': 'd',
|
||||
'е': 'e',
|
||||
'Ñ‘': 'e',
|
||||
'ж': 'zh',
|
||||
'з': 'z',
|
||||
'и': 'i',
|
||||
'й': 'j',
|
||||
'к': 'k',
|
||||
'л': 'l',
|
||||
'м': 'm',
|
||||
'н': 'n',
|
||||
'о': 'o',
|
||||
'п': 'p',
|
||||
'Ñ€': 'r',
|
||||
'Ñ': 'n',
|
||||
'Ñ‚': 't',
|
||||
'у': 'u',
|
||||
'Ñ„': 'f',
|
||||
'Ñ…': 'h',
|
||||
'ц': 'ts',
|
||||
'ч': 'ch',
|
||||
'ш': 'sh',
|
||||
'щ': 'sh',
|
||||
'ÑŠ': '',
|
||||
'Ñ‹': 'i',
|
||||
'ь': '',
|
||||
'Ñ': 'n',
|
||||
'ÑŽ': 'yu',
|
||||
'Ñ': 'ya',
|
||||
'Ñ–': 'j',
|
||||
'ѳ': 'f',
|
||||
'Ñ£': 'e',
|
||||
'ѵ': 'i',
|
||||
'Ñ•': 'z',
|
||||
'ѯ': 'ks',
|
||||
'ѱ': 'ps',
|
||||
'Ñ¡': 'o',
|
||||
'Ñ«': 'yu',
|
||||
'ѧ': 'ya',
|
||||
'Ñ': 'yu',
|
||||
'Ñ©': 'ya'
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
269
project/web/index/new/productos_bulk_update.php
Executable file
269
project/web/index/new/productos_bulk_update.php
Executable file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
|
||||
ini_set('max_execution_time', 120);
|
||||
set_time_limit(120);
|
||||
|
||||
$LANG_ES = (int) legacy_config('store.language_es', 4);
|
||||
$IMG_BASE = legacy_config('store.image_base_url', 'https://example.local/image/');
|
||||
$LOG_PATH = legacy_config('paths.worker_log', __DIR__ . '/logs/worker.log');
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'log_tail') {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$limit = isset($_GET['limit']) ? max(10, min(200, (int)$_GET['limit'])) : 80;
|
||||
if (!file_exists($LOG_PATH)) {
|
||||
echo json_encode(['ok' => false, 'message' => 'Aún no hay registros.']);
|
||||
} else {
|
||||
$lines = file($LOG_PATH, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$tail = array_slice($lines, -$limit);
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'updated' => date('H:i:s'),
|
||||
'log' => implode("\n", $tail)
|
||||
]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'show_log') {
|
||||
include('./inc/header.php');
|
||||
$logEndpoint = 'productos_bulk_update.php?action=log_tail&limit=120';
|
||||
echo "<div class='container'>
|
||||
<h3>📊 Progreso del worker</h3>
|
||||
<p>Puedes dejar esta ventana abierta para monitorear la cola en tiempo real.</p>
|
||||
<div class='row'>
|
||||
<div class='twelve columns'>
|
||||
<div class='log-panel'>
|
||||
<div class='log-panel__header'>
|
||||
<strong>Últimas entradas</strong>
|
||||
<span id='log-status' class='log-panel__status'>Actualizando...</span>
|
||||
</div>
|
||||
<pre id='log-viewer' class='log-panel__body'>Cargando log...</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='row'>
|
||||
<div class='twelve columns'>
|
||||
<button onclick=\"window.location.href='productos_bulk_update.php'\" class='button last'>← Volver al listado</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
const viewer = document.getElementById('log-viewer');
|
||||
const status = document.getElementById('log-status');
|
||||
const endpoint = '$logEndpoint';
|
||||
const refresh = () => {
|
||||
fetch(endpoint + '&_=' + Date.now(), {cache:'no-store'})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) {
|
||||
viewer.textContent = data.message || 'Sin datos disponibles.';
|
||||
status.textContent = 'Sin registros';
|
||||
return;
|
||||
}
|
||||
viewer.textContent = data.log || 'Sin registros recientes.';
|
||||
status.textContent = 'Actualizado ' + (data.updated || '');
|
||||
viewer.scrollTop = viewer.scrollHeight;
|
||||
})
|
||||
.catch(() => {
|
||||
status.textContent = 'Error al actualizar';
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
setInterval(refresh, 5000);
|
||||
})();
|
||||
</script>";
|
||||
include('./inc/footer.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
include('./inc/header.php');
|
||||
|
||||
$db = legacy_new_mysqli();
|
||||
if ($db->connect_errno) die("❌ Error DB: " . $db->connect_error);
|
||||
|
||||
/* ============================================
|
||||
🧾 Insertar productos seleccionados a la cola
|
||||
============================================ */
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['productos'])) {
|
||||
$productos = $_POST['productos'];
|
||||
$inserted = 0;
|
||||
foreach ($productos as $pid) {
|
||||
$pid = (int)$pid;
|
||||
$db->query("INSERT IGNORE INTO oc_product_queue (product_id) VALUES ($pid)");
|
||||
if ($db->affected_rows > 0) {
|
||||
$inserted++;
|
||||
}
|
||||
}
|
||||
$logEndpoint = 'productos_bulk_update.php?action=log_tail&limit=120';
|
||||
echo "<div class='container'>
|
||||
<h3>✅ $inserted productos añadidos a la cola.</h3>
|
||||
<p>El worker los procesará automáticamente por orden de llegada. Puedes seguir el progreso en tiempo real debajo.</p>
|
||||
<div class='row'>
|
||||
<div class='twelve columns'>
|
||||
<div class='log-panel'>
|
||||
<div class='log-panel__header'>
|
||||
<strong>📊 Log del worker (últimas entradas)</strong>
|
||||
<span id='log-status' class='log-panel__status'>Actualizando...</span>
|
||||
</div>
|
||||
<pre id='log-viewer' class='log-panel__body'>Cargando log...</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='row'>
|
||||
<div class='twelve columns'>
|
||||
<button class='button button-primary' onclick=\"window.location.href='productos_bulk_update.php'\">← Volver</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
const viewer = document.getElementById('log-viewer');
|
||||
const status = document.getElementById('log-status');
|
||||
const endpoint = '$logEndpoint';
|
||||
const refresh = () => {
|
||||
fetch(endpoint + '&_=' + Date.now(), {cache: 'no-store'})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) {
|
||||
viewer.textContent = data.message || 'Sin datos disponibles.';
|
||||
status.textContent = 'Sin registros';
|
||||
return;
|
||||
}
|
||||
viewer.textContent = data.log || 'Sin registros recientes.';
|
||||
status.textContent = 'Actualizado ' + (data.updated || '');
|
||||
viewer.scrollTop = viewer.scrollHeight;
|
||||
})
|
||||
.catch(() => {
|
||||
status.textContent = 'Error al actualizar';
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
setInterval(refresh, 5000);
|
||||
})();
|
||||
</script>";
|
||||
include('./inc/footer.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
📋 Listado de productos activos no procesados
|
||||
============================================ */
|
||||
$per_page = isset($_GET['per_page']) ? max(10, min(200, (int)$_GET['per_page'])) : 50;
|
||||
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
|
||||
|
||||
$sql_total = "
|
||||
SELECT COUNT(*) AS total
|
||||
FROM oc_product p
|
||||
WHERE p.status = 1
|
||||
AND p.product_id NOT IN (SELECT product_id FROM oc_product_queue)";
|
||||
$res_total = $db->query($sql_total);
|
||||
$total_rows = $res_total ? (int)$res_total->fetch_assoc()['total'] : 0;
|
||||
$total_pages = max(1, (int)ceil($total_rows / $per_page));
|
||||
if ($page > $total_pages) {
|
||||
$page = $total_pages;
|
||||
}
|
||||
$offset = ($page - 1) * $per_page;
|
||||
|
||||
$q = "
|
||||
SELECT p.product_id, p.image, d1.name, LEFT(d1.description, 250) AS descripcion
|
||||
FROM oc_product p
|
||||
LEFT JOIN oc_product_description d1
|
||||
ON p.product_id=d1.product_id AND d1.language_id=$LANG_ES
|
||||
WHERE p.status = 1
|
||||
AND p.product_id NOT IN (SELECT product_id FROM oc_product_queue)
|
||||
ORDER BY p.product_id ASC
|
||||
LIMIT $per_page OFFSET $offset";
|
||||
$res = $db->query($q);
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h3>🧾 Productos pendientes de optimización SEO</h3>
|
||||
<div class="row">
|
||||
<div class="eight columns">
|
||||
<p>Total pendientes: <strong><?php echo $total_rows; ?></strong><br>
|
||||
Página <strong><?php echo $page; ?></strong> de <strong><?php echo $total_pages; ?></strong></p>
|
||||
</div>
|
||||
<div class="four columns">
|
||||
<form method="GET" class="row u-cf">
|
||||
<div class="six columns">
|
||||
<label for="per_page">products</label>
|
||||
</div>
|
||||
<div class="six columns">
|
||||
<select class="u-full-width" name="per_page" id="per_page" onchange="this.form.submit()">
|
||||
<?php foreach ([25, 50, 100, 150, 200] as $opt): ?>
|
||||
<option value="<?php echo $opt; ?>" <?php echo $opt==$per_page?'selected':''; ?>><?php echo $opt; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="page" value="1">
|
||||
<noscript><div class="four columns"><button type="submit" class="button">Actualizar</button></div></noscript>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($total_rows === 0): ?>
|
||||
<div class="row">
|
||||
<div class="twelve columns alert-box">
|
||||
<p>No hay productos pendientes de encolar.</p>
|
||||
<button type="button" class="button" onclick="window.location.href='productos_bulk_update.php?action=show_log'">
|
||||
Ver progreso del worker
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
<table class="u-full-width">
|
||||
<thead>
|
||||
<tr><th></th><th>ID</th><th>Imagen</th><th>Nombre</th><th>Descripción</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php while($r=$res->fetch_assoc()){
|
||||
$img=!empty($r['image'])?$IMG_BASE.htmlspecialchars($r['image']):"https://via.placeholder.com/80x80?text=No+Image"; ?>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="productos[]" value="<?php echo $r['product_id']; ?>" checked></td>
|
||||
<td><?php echo $r['product_id']; ?></td>
|
||||
<td><img src="<?php echo $img; ?>" alt=""></td>
|
||||
<td><?php echo htmlspecialchars($r['name']); ?></td>
|
||||
<td><div class="table-description"><?php echo nl2br(strip_tags($r['descripcion'])); ?></div></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<button type="submit" class="button button-primary">➕ Encolar seleccionados</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="current_page" value="<?php echo $page; ?>">
|
||||
<input type="hidden" name="current_per_page" value="<?php echo $per_page; ?>">
|
||||
</form>
|
||||
|
||||
<?php
|
||||
$queryBase = function($targetPage) use ($per_page) {
|
||||
return 'productos_bulk_update.php?page=' . $targetPage . '&per_page=' . $per_page;
|
||||
};
|
||||
?>
|
||||
<div class="row pagination-row">
|
||||
<div class="twelve columns">
|
||||
<?php if($page > 1): ?>
|
||||
<a class="button" href="<?php echo $queryBase($page-1); ?>">← Página anterior</a>
|
||||
<?php else: ?>
|
||||
<span class="button disabled">← Página anterior</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<span class="pagination-meta">Página <?php echo $page; ?> de <?php echo $total_pages; ?></span>
|
||||
|
||||
<?php if($page < $total_pages): ?>
|
||||
<a class="button" href="<?php echo $queryBase($page+1); ?>">Página siguiente →</a>
|
||||
<?php else: ?>
|
||||
<span class="button disabled">Página siguiente →</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include('./inc/footer.php'); ?>
|
||||
284
project/web/index/new/productos_modificados.php
Normal file
284
project/web/index/new/productos_modificados.php
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
/* ============================
|
||||
productos_modificados.php
|
||||
============================ */
|
||||
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
|
||||
$LANG_ES = (int) legacy_config('store.language_es', 4);
|
||||
$LANG_EN = (int) legacy_config('store.language_en', 1);
|
||||
$IMG_BASE = legacy_config('store.image_base_url', 'https://example.local/image/');
|
||||
$PRODUCT_BASE = legacy_config('store.product_base_url', 'https://example.local/index.php?route=product/product&product_id=');
|
||||
|
||||
/* ---- Conexión BD ---- */
|
||||
$db = legacy_new_mysqli();
|
||||
if ($db->connect_errno) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(500);
|
||||
echo "DB Error: " . $db->connect_error;
|
||||
exit;
|
||||
}
|
||||
|
||||
/* ---- AJAX ---- */
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Toggle individual
|
||||
if (isset($_POST['pid']) && isset($_POST['toggle'])) {
|
||||
$pid = (int)$_POST['pid'];
|
||||
$state = (int)($_POST['toggle'] ? 1 : 0);
|
||||
$ok = $db->query("UPDATE oc_product_queue SET needs_verify=$state WHERE product_id=$pid");
|
||||
echo json_encode(['ok'=>$ok, 'state'=>$state]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Auto-marcar faltantes
|
||||
if (isset($_POST['autoMark'])) {
|
||||
$ok = $db->query("UPDATE oc_product_queue
|
||||
SET needs_verify=1
|
||||
WHERE processed=1 AND
|
||||
(product_id IN (
|
||||
SELECT p.product_id FROM oc_product p
|
||||
LEFT JOIN oc_product_description d1 ON p.product_id=d1.product_id AND d1.language_id=$LANG_ES
|
||||
LEFT JOIN oc_product_description d2 ON p.product_id=d2.product_id AND d2.language_id=$LANG_EN
|
||||
WHERE (d1.description IS NULL OR d1.description='')
|
||||
OR (d2.description IS NULL OR d2.description='')
|
||||
))");
|
||||
echo json_encode(['ok'=>$ok]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 🔁 Reprocesar Needs Verify
|
||||
if (isset($_POST['reprocessNeeds'])) {
|
||||
$ok = $db->query("UPDATE oc_product_queue
|
||||
SET processed=0, log=NULL
|
||||
WHERE needs_verify=1");
|
||||
echo json_encode(['ok'=>$ok]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Vista ---- */
|
||||
include('./inc/header.php'); // aquí ya se incluye custom.css
|
||||
|
||||
/* ---- Paginación ---- */
|
||||
$per_page = 50;
|
||||
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
|
||||
$offset = ($page - 1) * $per_page;
|
||||
|
||||
$search = isset($_GET['q']) ? trim($_GET['q']) : '';
|
||||
$filter_missing = isset($_GET['missing']) ? (int)$_GET['missing'] : 0;
|
||||
|
||||
$conditions = ["q.processed=1"];
|
||||
if ($search !== '') {
|
||||
$safe = $db->real_escape_string($search);
|
||||
$conditions[] = "(d1.name LIKE '%$safe%' OR d2.name LIKE '%$safe%')";
|
||||
}
|
||||
if ($filter_missing) {
|
||||
$conditions[] = "(
|
||||
(d1.description IS NULL OR d1.description='')
|
||||
OR (d2.description IS NULL OR d2.description='')
|
||||
OR (d1.meta_description IS NULL OR d1.meta_description='')
|
||||
OR (d2.meta_description IS NULL OR d2.meta_description='')
|
||||
)";
|
||||
}
|
||||
$where = implode(' AND ', $conditions);
|
||||
|
||||
$res_total = $db->query("SELECT COUNT(*) AS t
|
||||
FROM oc_product_queue q
|
||||
JOIN oc_product p ON q.product_id=p.product_id
|
||||
LEFT JOIN oc_product_description d1 ON p.product_id=d1.product_id AND d1.language_id=$LANG_ES
|
||||
LEFT JOIN oc_product_description d2 ON p.product_id=d2.product_id AND d2.language_id=$LANG_EN
|
||||
WHERE $where");
|
||||
$total = $res_total ? (int)$res_total->fetch_assoc()['t'] : 0;
|
||||
$total_pages = max(1, ceil($total / $per_page));
|
||||
|
||||
$sql = "
|
||||
SELECT p.product_id,p.image,
|
||||
d1.description AS descripcion_es,
|
||||
d2.description AS descripcion_en,
|
||||
d1.name AS nombre_es,
|
||||
d2.name AS nombre_en,
|
||||
q.processed_at,q.needs_verify
|
||||
FROM oc_product_queue q
|
||||
JOIN oc_product p ON q.product_id=p.product_id
|
||||
LEFT JOIN oc_product_description d1 ON p.product_id=d1.product_id AND d1.language_id=$LANG_ES
|
||||
LEFT JOIN oc_product_description d2 ON p.product_id=d2.product_id AND d2.language_id=$LANG_EN
|
||||
WHERE $where
|
||||
ORDER BY q.processed_at DESC
|
||||
LIMIT $per_page OFFSET $offset";
|
||||
$res = $db->query($sql);
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<h3>🧾 Productos modificados</h3>
|
||||
<p>Total: <b><?php echo $total; ?></b> — Página <b><?php echo $page; ?></b> de <b><?php echo $total_pages; ?></b></p>
|
||||
|
||||
<form method="GET">
|
||||
<div class="row" style="margin-bottom: 0;">
|
||||
<div class="nine columns">
|
||||
<input type="text"
|
||||
id="search-name"
|
||||
name="q"
|
||||
value="<?php echo htmlspecialchars($search); ?>"
|
||||
placeholder="Filtrar por nombre..."
|
||||
class="u-full-width">
|
||||
</div>
|
||||
<div class="three columns">
|
||||
<label for="missing" class="u-pull-right" style="margin-top:8px;">
|
||||
<input type="checkbox"
|
||||
id="missing"
|
||||
name="missing"
|
||||
value="1"
|
||||
<?php echo $filter_missing ? 'checked' : ''; ?>>
|
||||
Missing
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="six columns">
|
||||
<button type="submit" class="button button-primary u-full-width">Aplicar</button>
|
||||
</div>
|
||||
<div class="six columns">
|
||||
<a href="productos_modificados.php" class="button u-full-width">Limpiar</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div class="row toolbar">
|
||||
<div class="six columns">
|
||||
<button class="button u-full-width" onclick="autoMark()" type="button">⚙️ Auto-marcar faltantes</button>
|
||||
</div>
|
||||
<div class="six columns">
|
||||
<button class="button u-full-width" onclick="reprocessNeeds()" type="button">🔁 Reprocesar Needs Verify</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="u-full-width">
|
||||
<tr><th>Imagen</th><th>Descripciones</th></tr>
|
||||
<?php while($r=$res->fetch_assoc()):
|
||||
$img = !empty($r['image']) ? $IMG_BASE.htmlspecialchars($r['image']) : "https://via.placeholder.com/160";
|
||||
$url = $PRODUCT_BASE.$r['product_id'];
|
||||
$fecha = $r['processed_at']?date('d/m/Y H:i',strtotime($r['processed_at'])):'-';
|
||||
$has_en = !empty(trim($r['descripcion_en']));
|
||||
$has_es = !empty(trim($r['descripcion_es']));
|
||||
$flag_html = $r['needs_verify']
|
||||
? '<span class="badge warn">⚠️ Needs verify</span>'
|
||||
: '<span class="badge ok">✔ Verified</span>';
|
||||
$btn_text = $r['needs_verify'] ? '✅ Mark OK' : '⚠️ Mark Needs Verify';
|
||||
$btn_cls = $r['needs_verify'] ? 'toggle-btn active' : 'toggle-btn';
|
||||
?>
|
||||
<tr>
|
||||
<td rowspan="2" style="text-align:center;width:180px;">
|
||||
<a href="<?php echo $url;?>" target="_blank"><img src="<?php echo $img;?>" alt=""></a>
|
||||
<div class="idbox">
|
||||
ID <?php echo $r['product_id'];?><br>
|
||||
<small><?php echo htmlspecialchars($r['nombre_es'] ?: $r['nombre_en'] ?: ''); ?></small><br>
|
||||
<small><?php echo $fecha;?></small><br>
|
||||
<div id="flag-<?php echo $r['product_id'];?>"><?php echo $flag_html;?></div>
|
||||
<button class="<?php echo $btn_cls;?>" id="btn-<?php echo $r['product_id'];?>"
|
||||
onclick="toggleVerify(<?php echo $r['product_id'];?>)">
|
||||
<?php echo $btn_text;?>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="lang-title">🇬🇧 English
|
||||
<span class="badge <?php echo $has_en?'ok':'miss';?>"><?php echo $has_en?'OK':'Missing';?></span>
|
||||
</div>
|
||||
<div class="lang-section"><?php echo $has_en?$r['descripcion_en']:'<i>No data</i>';?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="lang-title">🇪🇸 Español
|
||||
<span class="badge <?php echo $has_es?'ok':'miss';?>"><?php echo $has_es?'OK':'Missing';?></span>
|
||||
</div>
|
||||
<div class="lang-section"><?php echo $has_es?$r['descripcion_es']:'<i>No data</i>';?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endwhile;?>
|
||||
</table>
|
||||
|
||||
<?php
|
||||
$queryBase = function($targetPage) use ($search, $filter_missing) {
|
||||
$params = ['page'=>$targetPage];
|
||||
if ($search !== '') $params['q'] = $search;
|
||||
if ($filter_missing) $params['missing'] = 1;
|
||||
return 'productos_modificados.php?' . http_build_query($params);
|
||||
};
|
||||
?>
|
||||
<div class="pagination">
|
||||
<?php if($page>1):?>
|
||||
<a href="<?php echo $queryBase($page-1);?>">← Prev</a>
|
||||
<?php else:?>
|
||||
<a class="disabled">← Prev</a>
|
||||
<?php endif;?>
|
||||
<span>Página <?php echo $page;?> de <?php echo $total_pages;?></span>
|
||||
<?php if($page<$total_pages):?>
|
||||
<a href="<?php echo $queryBase($page+1);?>">Next →</a>
|
||||
<?php else:?>
|
||||
<a class="disabled">Next →</a>
|
||||
<?php endif;?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleVerify(pid){
|
||||
const btn = document.getElementById('btn-'+pid);
|
||||
const flag = document.getElementById('flag-'+pid);
|
||||
const active = btn.classList.contains('active');
|
||||
const next = active ? 0 : 1;
|
||||
const params = new URLSearchParams({pid:pid, toggle:next});
|
||||
fetch(window.location.pathname+window.location.search,{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/x-www-form-urlencoded'},
|
||||
body:params.toString()
|
||||
}).then(r=>r.json()).then(j=>{
|
||||
if(!j.ok){alert('Error al actualizar');return;}
|
||||
if(j.state===1){
|
||||
btn.classList.add('active');
|
||||
btn.textContent='✅ Mark OK';
|
||||
flag.innerHTML='<span class="badge warn">⚠️ Needs verify</span>';
|
||||
}else{
|
||||
btn.classList.remove('active');
|
||||
btn.textContent='⚠️ Mark Needs Verify';
|
||||
flag.innerHTML='<span class="badge ok">✔ Verified</span>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function autoMark(){
|
||||
if(!confirm('¿Marcar automáticamente como "Need verification" todos los productos con descripciones faltantes?'))return;
|
||||
fetch('',{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/x-www-form-urlencoded'},
|
||||
body:'autoMark=1'
|
||||
}).then(r=>r.json()).then(j=>{
|
||||
if(j.ok){alert('Productos con descripciones faltantes marcados como Need Verify.');location.reload();}
|
||||
else alert('Error al marcar.');
|
||||
});
|
||||
}
|
||||
|
||||
function reprocessNeeds(){
|
||||
if(!confirm('¿Reprocesar todos los productos marcados como "Needs Verify"?'))return;
|
||||
fetch('',{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/x-www-form-urlencoded'},
|
||||
body:'reprocessNeeds=1'
|
||||
})
|
||||
.then(r=>r.json())
|
||||
.then(j=>{
|
||||
if(j.ok){
|
||||
alert('Los productos "Needs Verify" han sido marcados como pendientes para reprocesar. El worker los regenerará.');
|
||||
location.reload();
|
||||
} else alert('Error al actualizar.');
|
||||
})
|
||||
.catch(()=>alert('Error de red.'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php include('./inc/footer.php'); ?>
|
||||
|
||||
255
project/web/index/new/worker_bulk.php
Executable file
255
project/web/index/new/worker_bulk.php
Executable file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// worker_bulk.php — doble prompt: inglés y español independientes
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
|
||||
date_default_timezone_set('Europe/Madrid');
|
||||
mb_internal_encoding('UTF-8');
|
||||
@ini_set('max_execution_time', '0');
|
||||
@set_time_limit(0);
|
||||
|
||||
/* === CONFIG === */
|
||||
$OPENAI_API_KEY = trim((string) legacy_config('openai.api_key', ''));
|
||||
$OPENAI_MODEL = legacy_config('openai.model', 'gpt-4o-mini');
|
||||
$OPENAI_ENDPOINT = legacy_config('openai.endpoint', 'https://api.openai.com/v1/chat/completions');
|
||||
|
||||
$LANG_ES = (int) legacy_config('store.language_es', 4);
|
||||
$LANG_EN = (int) legacy_config('store.language_en', 1);
|
||||
$STORE_NAME = legacy_config('store.name', 'Natural - Mercado de Vida');
|
||||
|
||||
$LOG_FILE = legacy_config('paths.worker_log', __DIR__ . '/logs/worker.log');
|
||||
$PROMPT_EN_FILE = legacy_config('paths.prompt_en', __DIR__ . '/inc/prompt_en.md');
|
||||
$PROMPT_ES_FILE = legacy_config('paths.prompt_es', __DIR__ . '/inc/prompt_es.md');
|
||||
$BATCH_SIZE = (int) legacy_config('worker.batch_size', 20);
|
||||
$MIN_HTML_LENGTH = (int) legacy_config('worker.min_html_length', 500);
|
||||
|
||||
$SHARD_TOTAL = 1;
|
||||
$SHARD_INDEX = 0;
|
||||
|
||||
if (PHP_SAPI === 'cli' && isset($argv)) {
|
||||
foreach ($argv as $arg) {
|
||||
if (strpos($arg, '--shards=') === 0) {
|
||||
$value = (int)substr($arg, 9);
|
||||
if ($value > 0) $SHARD_TOTAL = min($value, 16); // evita saturar en exceso
|
||||
} elseif (strpos($arg, '--shard=') === 0) {
|
||||
$value = (int)substr($arg, 8);
|
||||
if ($value >= 0) $SHARD_INDEX = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($SHARD_INDEX >= $SHARD_TOTAL) {
|
||||
$SHARD_INDEX = $SHARD_TOTAL - 1;
|
||||
}
|
||||
if ($SHARD_INDEX < 0) $SHARD_INDEX = 0;
|
||||
|
||||
/* === FUNCIONES === */
|
||||
function log_msg($msg) {
|
||||
global $LOG_FILE;
|
||||
$time = date('Y-m-d H:i:s');
|
||||
file_put_contents($LOG_FILE, "[$time] $msg\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
function obtener_respuesta($prompt, $key, $model, $max_tokens = 2000, $retries = 3) {
|
||||
$endpoint = legacy_config('openai.endpoint', 'https://api.openai.com/v1/chat/completions');
|
||||
|
||||
if ($key === '' || strpos($key, 'CHANGE_ME_') === 0) {
|
||||
log_msg('❌ Missing openai.api_key in config/local.php');
|
||||
return '';
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $retries; $i++) {
|
||||
$ch = curl_init($endpoint);
|
||||
$data = [
|
||||
'model' => $model,
|
||||
'messages' => [['role' => 'user', 'content' => $prompt]],
|
||||
'temperature' => 0.6,
|
||||
'max_tokens' => $max_tokens
|
||||
];
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . trim($key)
|
||||
],
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($data),
|
||||
CURLOPT_TIMEOUT => 180
|
||||
]);
|
||||
$result = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($err) { log_msg("⚠️ cURL error ($i/$retries): $err"); sleep(2); continue; }
|
||||
if ($http !== 200) { log_msg("⚠️ HTTP $http on attempt $i"); sleep(3); continue; }
|
||||
|
||||
$json = json_decode($result, true);
|
||||
$txt = $json['choices'][0]['message']['content'] ?? '';
|
||||
if ($txt && mb_strlen(trim($txt)) > 50) return trim($txt);
|
||||
|
||||
log_msg("⚠️ Empty response attempt $i");
|
||||
sleep(2);
|
||||
}
|
||||
log_msg("❌ No response after $retries attempts");
|
||||
return '';
|
||||
}
|
||||
|
||||
function limpiar_html($t) {
|
||||
if (!$t) return '';
|
||||
|
||||
// 🔧 Quita fences Markdown (```html ... ```)
|
||||
$t = preg_replace('/^```[a-zA-Z]*\s*/m', '', $t);
|
||||
$t = preg_replace('/```$/m', '', $t);
|
||||
$t = preg_replace('/```[\s\S]*?```/', '', $t);
|
||||
|
||||
// Quita h1/h2 pero conserva contenido
|
||||
$t = preg_replace('/<\/?h1[^>]*>/i', '', $t);
|
||||
$t = preg_replace('/<\/?h2[^>]*>/i', '', $t);
|
||||
|
||||
// Convierte div y section a <p>
|
||||
$t = preg_replace('/<\s*div[^>]*>/i', '<p>', $t);
|
||||
$t = preg_replace('/<\s*\/div\s*>/i', '</p>', $t);
|
||||
$t = preg_replace('/<\s*section[^>]*>/i', '<p>', $t);
|
||||
$t = preg_replace('/<\s*\/section\s*>/i', '</p>', $t);
|
||||
|
||||
// Quita scripts y estilos
|
||||
$t = preg_replace('/<script.*?<\/script>/is', '', $t);
|
||||
$t = preg_replace('/<style.*?<\/style>/is', '', $t);
|
||||
|
||||
// Quita markdown residual
|
||||
$t = str_replace('```', '', $t);
|
||||
|
||||
// Limpieza de espacios
|
||||
$t = preg_replace('/[ \t]+/', ' ', $t);
|
||||
$t = preg_replace('/\n{2,}/', "\n", $t);
|
||||
return trim($t);
|
||||
}
|
||||
|
||||
/* Elimina emojis y normaliza espacios */
|
||||
function sanitize_for_db($text) {
|
||||
if ($text === null || $text === '') return '';
|
||||
$text = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $text);
|
||||
$text = preg_replace('/\s+/', ' ', $text);
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
function sentence_case($text) {
|
||||
if (empty($text)) return '';
|
||||
$text = trim(mb_strtolower($text, 'UTF-8'));
|
||||
$first = mb_strtoupper(mb_substr($text, 0, 1, 'UTF-8'), 'UTF-8');
|
||||
return $first . mb_substr($text, 1, null, 'UTF-8');
|
||||
}
|
||||
|
||||
/* === DB === */
|
||||
$db = legacy_new_mysqli();
|
||||
if ($db->connect_errno) { log_msg('❌ DB: ' . $db->connect_error); exit; }
|
||||
|
||||
/* === Prompt base === */
|
||||
if (!file_exists($PROMPT_EN_FILE) || !file_exists($PROMPT_ES_FILE)) {
|
||||
log_msg("❌ Missing prompt files.");
|
||||
exit;
|
||||
}
|
||||
$PROMPT_EN = file_get_contents($PROMPT_EN_FILE);
|
||||
$PROMPT_ES = file_get_contents($PROMPT_ES_FILE);
|
||||
if (trim($PROMPT_EN) === '' || trim($PROMPT_ES) === '') {
|
||||
log_msg("❌ Empty prompt files.");
|
||||
exit;
|
||||
}
|
||||
|
||||
/* === Worker === */
|
||||
$shardLabel = $SHARD_TOTAL > 1 ? " | shard {$SHARD_INDEX}/{$SHARD_TOTAL}" : '';
|
||||
log_msg("🚀 Worker iniciado (modo doble prompt, batch={$BATCH_SIZE}{$shardLabel})");
|
||||
|
||||
$shardFilter = $SHARD_TOTAL > 1 ? " AND MOD(id, {$SHARD_TOTAL}) = {$SHARD_INDEX}" : '';
|
||||
$q = $db->query("SELECT * FROM oc_product_queue WHERE processed=0{$shardFilter} ORDER BY id ASC LIMIT $BATCH_SIZE");
|
||||
if (!$q || $q->num_rows === 0) { log_msg("⏸️ Cola vacía."); exit; }
|
||||
|
||||
while ($row = $q->fetch_assoc()) {
|
||||
$pid = (int)$row['product_id'];
|
||||
log_msg("🔄 Procesando producto $pid...");
|
||||
|
||||
$r = $db->query("
|
||||
SELECT p.ean, d.name
|
||||
FROM oc_product p
|
||||
LEFT JOIN oc_product_description d ON p.product_id=d.product_id AND d.language_id=$LANG_ES
|
||||
WHERE p.product_id=$pid
|
||||
");
|
||||
if (!$r || !$prod = $r->fetch_assoc()) {
|
||||
log_msg("⚠️ Producto $pid no encontrado");
|
||||
$db->query("UPDATE oc_product_queue SET processed=1, log='No encontrado' WHERE product_id=$pid");
|
||||
continue;
|
||||
}
|
||||
|
||||
$producto = $prod['name'];
|
||||
$ean = $prod['ean'];
|
||||
|
||||
// === Prompts personalizados ===
|
||||
$prompt_en = str_replace(['$producto', '$ean'], [$producto, $ean], $PROMPT_EN);
|
||||
$prompt_es = str_replace(['$producto', '$ean'], [$producto, $ean], $PROMPT_ES);
|
||||
|
||||
// === Generar EN ===
|
||||
$raw_en = obtener_respuesta($prompt_en, $OPENAI_API_KEY, $OPENAI_MODEL, 2200);
|
||||
file_put_contents(__DIR__ . "/logs/raw_openai_en_$pid.txt", $raw_en);
|
||||
|
||||
$clean_en = limpiar_html($raw_en);
|
||||
$html_en = sanitize_for_db($clean_en);
|
||||
$meta_en = sanitize_for_db(mb_substr(strip_tags($clean_en), 0, 255, 'UTF-8'));
|
||||
|
||||
// === Generar ES ===
|
||||
$raw_es = obtener_respuesta($prompt_es, $OPENAI_API_KEY, $OPENAI_MODEL, 2200);
|
||||
file_put_contents(__DIR__ . "/logs/raw_openai_es_$pid.txt", $raw_es);
|
||||
|
||||
$clean_es = limpiar_html($raw_es);
|
||||
$html_es = sanitize_for_db($clean_es);
|
||||
$meta_es = sanitize_for_db(mb_substr(strip_tags($clean_es), 0, 255, 'UTF-8'));
|
||||
|
||||
// === Longitud de contenido ===
|
||||
$len_en = mb_strlen($html_en);
|
||||
$len_es = mb_strlen($html_es);
|
||||
file_put_contents(__DIR__ . "/logs/html_debug_$pid.txt",
|
||||
"EN ($len_en):\n$html_en\n\nES ($len_es):\n$html_es"
|
||||
);
|
||||
|
||||
if ($len_en < $MIN_HTML_LENGTH || $len_es < $MIN_HTML_LENGTH) {
|
||||
log_msg("❌ Texto demasiado corto (EN=$len_en / ES=$len_es) PID $pid");
|
||||
$db->query("UPDATE oc_product_queue
|
||||
SET processed=1, processed_at=NOW(), result_en=0, result_es=0, needs_verify=1, log='Texto corto'
|
||||
WHERE product_id=$pid");
|
||||
continue;
|
||||
}
|
||||
|
||||
// === Guardar ===
|
||||
$u_title_en = sentence_case("$producto | $STORE_NAME");
|
||||
$u_h1_en = $producto;
|
||||
$u_h2_en = sentence_case("benefits and properties of $producto");
|
||||
|
||||
$u_title_es = sentence_case("comprar $producto | $STORE_NAME");
|
||||
$u_h1_es = $producto;
|
||||
$u_h2_es = sentence_case("propiedades y beneficios de $producto");
|
||||
|
||||
$stmt = $db->prepare("UPDATE oc_product_description
|
||||
SET description=?, meta_description=?, u_title=?, u_h1=?, u_h2=?
|
||||
WHERE product_id=? AND language_id=?");
|
||||
$stmt->bind_param('ssssssi', $html_en, $meta_en, $u_title_en, $u_h1_en, $u_h2_en, $pid, $LANG_EN);
|
||||
if (!$stmt->execute()) log_msg("❌ Error EN $pid: " . $stmt->error);
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("UPDATE oc_product_description
|
||||
SET description=?, meta_description=?, u_title=?, u_h1=?, u_h2=?
|
||||
WHERE product_id=? AND language_id=?");
|
||||
$stmt->bind_param('ssssssi', $html_es, $meta_es, $u_title_es, $u_h1_es, $u_h2_es, $pid, $LANG_ES);
|
||||
if (!$stmt->execute()) log_msg("❌ Error ES $pid: " . $stmt->error);
|
||||
$stmt->close();
|
||||
|
||||
$db->query("UPDATE oc_product_queue
|
||||
SET processed=1, processed_at=NOW(), result_en=1, result_es=1, needs_verify=0, log='OK doble prompt'
|
||||
WHERE product_id=$pid");
|
||||
|
||||
log_msg("✅ $pid completado EN/ES (len EN=$len_en | ES=$len_es)");
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
log_msg("🏁 Worker finalizado.");
|
||||
Reference in New Issue
Block a user