generazione automatica alias

This commit is contained in:
2025-08-26 11:47:03 +02:00
parent edb8d4e873
commit 2f251bbfb0
7 changed files with 364 additions and 270 deletions

View File

@ -14,6 +14,10 @@
<field name="created_by" type="createdby" default="0" label="JGLOBAL_FIELD_CREATED_BY_LABEL" description="JGLOBAL_FIELD_CREATED_BY_DESC" hidden="true" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_CREATED_BY"/> <field name="created_by" type="createdby" default="0" label="JGLOBAL_FIELD_CREATED_BY_LABEL" description="JGLOBAL_FIELD_CREATED_BY_DESC" hidden="true" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_CREATED_BY"/>
<field name="modified_by" type="modifiedby" default="0" label="JGLOBAL_FIELD_MODIFIED_BY_LABEL" description="JGLOBAL_FIELD_MODIFIED_BY_DESC" hidden="true" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_MODIFIED_BY"/> <field name="modified_by" type="modifiedby" default="0" label="JGLOBAL_FIELD_MODIFIED_BY_LABEL" description="JGLOBAL_FIELD_MODIFIED_BY_DESC" hidden="true" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_MODIFIED_BY"/>
<field name="title" filter="safehtml" type="text" label="JGLOBAL_TITLE" description="JFIELD_TITLE_DESC" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_TITLE"/> <field name="title" filter="safehtml" type="text" label="JGLOBAL_TITLE" description="JFIELD_TITLE_DESC" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_TITLE"/>
<field name="alias"
type="text"
label="JFIELD_ALIAS_LABEL"
description="JFIELD_ALIAS_DESC" />
<field name="description" filter="safehtml" type="textarea" label="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_DESCRIPTION" description="COM_CIRCOLARI_FORM_DESC_CIRCOLARE_DESCRIPTION" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_DESCRIPTION"/> <field name="description" filter="safehtml" type="textarea" label="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_DESCRIPTION" description="COM_CIRCOLARI_FORM_DESC_CIRCOLARE_DESCRIPTION" hint="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_DESCRIPTION"/>
<field name="attachment" type="media" <field name="attachment" type="media"
label="COM_CIRCOLARI_FIELD_ATTACHMENT_LABEL" label="COM_CIRCOLARI_FIELD_ATTACHMENT_LABEL"

View File

@ -1 +1,9 @@
/* Only premium users are allowed to update a component */ /* Only premium users are allowed to update a component */
ALTER TABLE `#__circolari`
ADD COLUMN `alias` VARCHAR(255) NOT NULL DEFAULT '' AFTER `title`,
ADD INDEX `idx_alias` (`alias`);
-- (facoltativo) prima valorizzazione "grezza" per i record esistenti
UPDATE `#__circolari`
SET `alias` = LOWER(REPLACE(TRIM(`title`), ' ', '-'))
WHERE (`alias` = '' OR `alias` IS NULL) AND `title` IS NOT NULL;

View File

@ -0,0 +1 @@
/* Only premium users are allowed to update a component */

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* @version CVS: 1.0.0 * @version CVS: 1.0.0
* @package Com_Circolari * @package Com_Circolari
@ -20,6 +21,7 @@ use \Joomla\CMS\Helper\TagsHelper;
use \Joomla\CMS\Filter\OutputFilter; use \Joomla\CMS\Filter\OutputFilter;
use \Joomla\CMS\Event\Model; use \Joomla\CMS\Event\Model;
use Joomla\CMS\Event\AbstractEvent; use Joomla\CMS\Event\AbstractEvent;
use Joomla\CMS\Application\ApplicationHelper;
/** /**
@ -29,249 +31,289 @@ use Joomla\CMS\Event\AbstractEvent;
*/ */
class CircolareModel extends AdminModel class CircolareModel extends AdminModel
{ {
/** /**
* @var string The prefix to use with controller messages. * @var string The prefix to use with controller messages.
* *
* @since 1.0.0 * @since 1.0.0
*/ */
protected $text_prefix = 'COM_CIRCOLARI'; protected $text_prefix = 'COM_CIRCOLARI';
/** /**
* @var string Alias to manage history control * @var string Alias to manage history control
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public $typeAlias = 'com_circolari.circolare'; public $typeAlias = 'com_circolari.circolare';
/** /**
* @var null Item data * @var null Item data
* *
* @since 1.0.0 * @since 1.0.0
*/ */
protected $item = null; protected $item = null;
/** /**
* Returns a reference to the a Table object, always creating it. * Returns a reference to the a Table object, always creating it.
* *
* @param string $type The table type to instantiate * @param string $type The table type to instantiate
* @param string $prefix A prefix for the table class name. Optional. * @param string $prefix A prefix for the table class name. Optional.
* @param array $config Configuration array for model. Optional. * @param array $config Configuration array for model. Optional.
* *
* @return Table A database object * @return Table A database object
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public function getTable($type = 'Circolare', $prefix = 'Administrator', $config = array()) public function getTable($type = 'Circolare', $prefix = 'Administrator', $config = array())
{ {
return parent::getTable($type, $prefix, $config); return parent::getTable($type, $prefix, $config);
} }
/** /**
* Method to get the record form. * Method to get the record form.
* *
* @param array $data An optional array of data for the form to interogate. * @param array $data An optional array of data for the form to interogate.
* @param boolean $loadData True if the form is to load its own data (default case), false if not. * @param boolean $loadData True if the form is to load its own data (default case), false if not.
* *
* @return \JForm|boolean A \JForm object on success, false on failure * @return \JForm|boolean A \JForm object on success, false on failure
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public function getForm($data = array(), $loadData = true) public function getForm($data = array(), $loadData = true)
{ {
// Initialise variables. // Initialise variables.
$app = Factory::getApplication(); $app = Factory::getApplication();
// Get the form. // Get the form.
$form = $this->loadForm( $form = $this->loadForm(
'com_circolari.circolare', 'com_circolari.circolare',
'circolare', 'circolare',
array( array(
'control' => 'jform', 'control' => 'jform',
'load_data' => $loadData 'load_data' => $loadData
) )
); );
if (empty($form)) if (empty($form)) {
{ return false;
return false; }
}
return $form; return $form;
} }
/** /**
* Method to get the data that should be injected in the form. * Method to get the data that should be injected in the form.
* *
* @return mixed The data for the form. * @return mixed The data for the form.
* *
* @since 1.0.0 * @since 1.0.0
*/ */
protected function loadFormData() protected function loadFormData()
{ {
// Check the session for previously entered form data. // Check the session for previously entered form data.
$data = Factory::getApplication()->getUserState('com_circolari.edit.circolare.data', array()); $data = Factory::getApplication()->getUserState('com_circolari.edit.circolare.data', array());
if (empty($data)) if (empty($data)) {
{ if ($this->item === null) {
if ($this->item === null) $this->item = $this->getItem();
{ }
$this->item = $this->getItem();
}
$data = $this->item; $data = $this->item;
}
} return $data;
}
return $data; /**
} * Method to get a single record.
*
* @param integer $pk The id of the primary key.
*
* @return mixed Object on success, false on failure.
*
* @since 1.0.0
*/
public function getItem($pk = null)
{
/** if ($item = parent::getItem($pk)) {
* Method to get a single record. if (isset($item->params)) {
* $item->params = json_encode($item->params);
* @param integer $pk The id of the primary key. }
*
* @return mixed Object on success, false on failure.
*
* @since 1.0.0
*/
public function getItem($pk = null)
{
if ($item = parent::getItem($pk)) // Do any procesing on fields here if needed
{ }
if (isset($item->params))
{
$item->params = json_encode($item->params);
}
// Do any procesing on fields here if needed return $item;
} }
return $item; /**
* Method to duplicate an Circolare
} *
* @param array &$pks An array of primary key IDs.
/** *
* Method to duplicate an Circolare * @return boolean True if successful.
* *
* @param array &$pks An array of primary key IDs. * @throws Exception
* */
* @return boolean True if successful. public function duplicate(&$pks)
* {
* @throws Exception $app = Factory::getApplication();
*/ $user = $app->getIdentity();
public function duplicate(&$pks)
{
$app = Factory::getApplication();
$user = $app->getIdentity();
$dispatcher = $this->getDispatcher(); $dispatcher = $this->getDispatcher();
// Access checks. // Access checks.
if (!$user->authorise('core.create', 'com_circolari')) if (!$user->authorise('core.create', 'com_circolari')) {
{ throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED'));
throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED')); }
}
$context = $this->option . '.' . $this->name; $context = $this->option . '.' . $this->name;
// Include the plugins for the save events. // Include the plugins for the save events.
PluginHelper::importPlugin($this->events_map['save']); PluginHelper::importPlugin($this->events_map['save']);
$table = $this->getTable(); $table = $this->getTable();
foreach ($pks as $pk) foreach ($pks as $pk) {
{
if ($table->load($pk, true)) if ($table->load($pk, true)) {
{ // Reset the id to create a new record.
// Reset the id to create a new record. $table->id = 0;
$table->id = 0;
if (!$table->check()) if (!$table->check()) {
{ throw new \Exception($table->getError());
throw new \Exception($table->getError()); }
}
// Create the before save event. // Create the before save event.
$beforeSaveEvent = AbstractEvent::create( $beforeSaveEvent = AbstractEvent::create(
$this->event_before_save, $this->event_before_save,
[ [
'context' => $context, 'context' => $context,
'subject' => $table, 'subject' => $table,
'isNew' => true, 'isNew' => true,
'data' => $table, 'data' => $table,
] ]
); );
// Trigger the before save event. // Trigger the before save event.
$dispatchResult = Factory::getApplication()->getDispatcher()->dispatch($this->event_before_save, $beforeSaveEvent); $dispatchResult = Factory::getApplication()->getDispatcher()->dispatch($this->event_before_save, $beforeSaveEvent);
// Check if dispatch result is an array and handle accordingly // Check if dispatch result is an array and handle accordingly
$result = isset($dispatchResult['result']) ? $dispatchResult['result'] : []; $result = isset($dispatchResult['result']) ? $dispatchResult['result'] : [];
// Proceed with your logic // Proceed with your logic
if (in_array(false, $result, true) || !$table->store()) { if (in_array(false, $result, true) || !$table->store()) {
throw new \Exception($table->getError()); throw new \Exception($table->getError());
} }
// Trigger the after save event. // Trigger the after save event.
Factory::getApplication()->getDispatcher()->dispatch( Factory::getApplication()->getDispatcher()->dispatch(
$this->event_after_save, $this->event_after_save,
AbstractEvent::create( AbstractEvent::create(
$this->event_after_save, $this->event_after_save,
[ [
'context' => $context, 'context' => $context,
'subject' => $table, 'subject' => $table,
'isNew' => true, 'isNew' => true,
'data' => $table, 'data' => $table,
] ]
) )
); );
} } else {
else throw new \Exception($table->getError());
{ }
throw new \Exception($table->getError()); }
}
} // Clean cache
$this->cleanCache();
// Clean cache return true;
$this->cleanCache(); }
return true; /**
} * Prepare and sanitise the table prior to saving.
*
* @param Table $table Table Object
*
* @return void
*
* @since 1.0.0
*/
protected function prepareTable($table)
{
jimport('joomla.filter.output');
/** if (empty($table->id)) {
* Prepare and sanitise the table prior to saving. // Set ordering to the last item if not set
* if (@$table->ordering === '') {
* @param Table $table Table Object $db = $this->getDbo();
* $db->setQuery('SELECT MAX(ordering) FROM #__circolari');
* @return void $max = $db->loadResult();
* $table->ordering = $max + 1;
* @since 1.0.0 }
*/ }
protected function prepareTable($table) }
{
jimport('joomla.filter.output');
if (empty($table->id)) public function check()
{ {
// Set ordering to the last item if not set // ordering per nuovi record
if (@$table->ordering === '') if (property_exists($this, 'ordering') && (int) $this->id === 0) {
{ $this->ordering = self::getNextOrder();
$db = $this->getDbo(); }
$db->setQuery('SELECT MAX(ordering) FROM #__circolari');
$max = $db->loadResult(); // Titolo obbligatorio (se vuoi forzarlo)
$table->ordering = $max + 1; $this->title = trim((string) ($this->title ?? ''));
} if ($this->title === '') {
} $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE'));
} return false;
}
// --- ALIAS: genera se vuoto, ripulisci, evita "solo numeri", garantisci univocità ---
$this->alias = trim((string) ($this->alias ?? ''));
// Se mancante → dal titolo
if ($this->alias === '') {
$this->alias = ApplicationHelper::stringURLSafe($this->title);
} else {
$this->alias = ApplicationHelper::stringURLSafe($this->alias);
}
// Evita alias vuoto o numerico puro
if ($this->alias === '' || ctype_digit($this->alias)) {
$seed = (int) ($this->id ?: time());
$this->alias = ApplicationHelper::stringURLSafe($this->title . '-' . $seed);
}
// Unicità alias nel contesto tabella
$base = $this->alias;
$i = 2;
while ($this->aliasExists($this->alias, (int) $this->id)) {
$this->alias = $base . '-' . $i;
$i++;
}
return parent::check();
}
protected function aliasExists(string $alias, int $excludeId = 0): bool
{
$q = $this->_db->getQuery(true)
->select('COUNT(*)')
->from($this->_db->quoteName('#__circolari'))
->where($this->_db->quoteName('alias') . ' = ' . $this->_db->quote($alias));
if ($excludeId > 0) {
$q->where($this->_db->quoteName('id') . ' != ' . (int) $excludeId);
}
$this->_db->setQuery($q);
return ((int) $this->_db->loadResult()) > 0;
}
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* @version CVS: 1.0.0 * @version CVS: 1.0.0
* @package Com_Circolari * @package Com_Circolari
@ -25,6 +26,7 @@ use \Joomla\CMS\Filesystem\File;
use \Joomla\Registry\Registry; use \Joomla\Registry\Registry;
use \Pcrt\Component\Circolari\Administrator\Helper\CircolariHelper; use \Pcrt\Component\Circolari\Administrator\Helper\CircolariHelper;
use \Joomla\CMS\Helper\ContentHelper; use \Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Application\ApplicationHelper;
/** /**
@ -37,12 +39,12 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
use TaggableTableTrait; use TaggableTableTrait;
/** /**
* Indicates that columns fully support the NULL value in the database * Indicates that columns fully support the NULL value in the database
* *
* @var boolean * @var boolean
* @since 4.0.0 * @since 4.0.0
*/ */
protected $_supportNullValue = true; protected $_supportNullValue = true;
/** /**
@ -55,7 +57,6 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
$this->typeAlias = 'com_circolari.circolare'; $this->typeAlias = 'com_circolari.circolare';
parent::__construct('#__circolari', 'id', $db); parent::__construct('#__circolari', 'id', $db);
$this->setColumnAlias('published', 'state'); $this->setColumnAlias('published', 'state');
} }
/** /**
@ -91,37 +92,31 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
$input = Factory::getApplication()->input; $input = Factory::getApplication()->input;
$task = $input->getString('task', ''); $task = $input->getString('task', '');
if ($array['id'] == 0 && empty($array['created_by'])) if ($array['id'] == 0 && empty($array['created_by'])) {
{
$array['created_by'] = Factory::getUser()->id; $array['created_by'] = Factory::getUser()->id;
} }
if ($array['id'] == 0 && empty($array['modified_by'])) if ($array['id'] == 0 && empty($array['modified_by'])) {
{
$array['modified_by'] = Factory::getUser()->id; $array['modified_by'] = Factory::getUser()->id;
} }
if ($task == 'apply' || $task == 'save') if ($task == 'apply' || $task == 'save') {
{
$array['modified_by'] = Factory::getUser()->id; $array['modified_by'] = Factory::getUser()->id;
} }
if (isset($array['params']) && is_array($array['params'])) if (isset($array['params']) && is_array($array['params'])) {
{
$registry = new Registry; $registry = new Registry;
$registry->loadArray($array['params']); $registry->loadArray($array['params']);
$array['params'] = (string) $registry; $array['params'] = (string) $registry;
} }
if (isset($array['metadata']) && is_array($array['metadata'])) if (isset($array['metadata']) && is_array($array['metadata'])) {
{
$registry = new Registry; $registry = new Registry;
$registry->loadArray($array['metadata']); $registry->loadArray($array['metadata']);
$array['metadata'] = (string) $registry; $array['metadata'] = (string) $registry;
} }
if (!$user->authorise('core.admin', 'com_circolari.circolare.' . $array['id'])) if (!$user->authorise('core.admin', 'com_circolari.circolare.' . $array['id'])) {
{
$actions = Access::getActionsFromFile( $actions = Access::getActionsFromFile(
JPATH_ADMINISTRATOR . '/components/com_circolari/access.xml', JPATH_ADMINISTRATOR . '/components/com_circolari/access.xml',
"/access/section[@name='circolare']/" "/access/section[@name='circolare']/"
@ -129,10 +124,8 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
$default_actions = Access::getAssetRules('com_circolari.circolare.' . $array['id'])->getData(); $default_actions = Access::getAssetRules('com_circolari.circolare.' . $array['id'])->getData();
$array_jaccess = array(); $array_jaccess = array();
foreach ($actions as $action) foreach ($actions as $action) {
{ if (key_exists($action->name, $default_actions)) {
if (key_exists($action->name, $default_actions))
{
$array_jaccess[$action->name] = $default_actions[$action->name]; $array_jaccess[$action->name] = $default_actions[$action->name];
} }
} }
@ -141,8 +134,7 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
} }
// Bind the rules for ACL where supported. // Bind the rules for ACL where supported.
if (isset($array['rules']) && is_array($array['rules'])) if (isset($array['rules']) && is_array($array['rules'])) {
{
$this->setRules($array['rules']); $this->setRules($array['rules']);
} }
@ -179,14 +171,11 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
{ {
$rules = array(); $rules = array();
foreach ($jaccessrules as $action => $jaccess) foreach ($jaccessrules as $action => $jaccess) {
{
$actions = array(); $actions = array();
if ($jaccess) if ($jaccess) {
{ foreach ($jaccess->getData() as $group => $allow) {
foreach ($jaccess->getData() as $group => $allow)
{
$actions[$group] = ((bool)$allow); $actions[$group] = ((bool)$allow);
} }
} }
@ -205,11 +194,45 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
public function check() public function check()
{ {
// If there is an ordering column and this is a new row then get the next ordering value // If there is an ordering column and this is a new row then get the next ordering value
if (property_exists($this, 'ordering') && $this->id == 0) if (property_exists($this, 'ordering') && $this->id == 0) {
{
$this->ordering = self::getNextOrder(); $this->ordering = self::getNextOrder();
} }
// Ordering per nuovi record
if (property_exists($this, 'ordering') && (int) $this->id === 0) {
$this->ordering = self::getNextOrder();
}
// Titolo obbligatorio (se preferisci non forzarlo, rimuovi questo blocco)
$this->title = trim((string) ($this->title ?? ''));
if ($this->title === '') {
$this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE'));
return false;
}
// --- ALIAS: genera se vuoto, pulisci, evita solo-numeri, rendilo unico ---
$this->alias = trim((string) ($this->alias ?? ''));
// Se non presente → dal titolo
if ($this->alias === '') {
$this->alias = ApplicationHelper::stringURLSafe($this->title);
} else {
$this->alias = ApplicationHelper::stringURLSafe($this->alias);
}
// Evita alias vuoto o numerico puro
if ($this->alias === '' || ctype_digit($this->alias)) {
$seed = (int) ($this->id ?: time());
$this->alias = ApplicationHelper::stringURLSafe($this->title . '-' . $seed);
}
// Unicità alias nella tabella
$base = $this->alias;
$i = 2;
while ($this->aliasExists($this->alias, (int) $this->id)) {
$this->alias = $base . '-' . $i;
$i++;
}
return parent::check(); return parent::check();
@ -251,8 +274,7 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
$assetParent->loadByName('com_circolari'); $assetParent->loadByName('com_circolari');
// Return the found asset-parent-id // Return the found asset-parent-id
if ($assetParent->id) if ($assetParent->id) {
{
$assetParentId = $assetParent->id; $assetParentId = $assetParent->id;
} }
@ -262,18 +284,34 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
//XXX_CUSTOM_TABLE_FUNCTION //XXX_CUSTOM_TABLE_FUNCTION
/** /**
* Delete a record by id * Delete a record by id
* *
* @param mixed $pk Primary key value to delete. Optional * @param mixed $pk Primary key value to delete. Optional
* *
* @return bool * @return bool
*/ */
public function delete($pk = null) public function delete($pk = null)
{ {
$this->load($pk); $this->load($pk);
$result = parent::delete($pk); $result = parent::delete($pk);
return $result;
}
protected function aliasExists(string $alias, int $excludeId = 0): bool
{
$q = $this->_db->getQuery(true)
->select('COUNT(*)')
->from($this->_db->quoteName('#__circolari'))
->where($this->_db->quoteName('alias') . ' = ' . $this->_db->quote($alias));
if ($excludeId > 0) {
$q->where($this->_db->quoteName('id') . ' != ' . (int) $excludeId);
}
$this->_db->setQuery($q);
return ((int) $this->_db->loadResult()) > 0;
}
return $result;
}
} }

View File

@ -35,6 +35,7 @@ HTMLHelper::_('bootstrap.tooltip');
<fieldset class="adminform"> <fieldset class="adminform">
<legend><?php echo Text::_('COM_CIRCOLARI_FIELDSET_CIRCOLARI'); ?></legend> <legend><?php echo Text::_('COM_CIRCOLARI_FIELDSET_CIRCOLARI'); ?></legend>
<?php echo $this->form->renderField('title'); ?> <?php echo $this->form->renderField('title'); ?>
<?php echo $this->form->renderField('alias'); ?>
<?php echo $this->form->renderField('description'); ?> <?php echo $this->form->renderField('description'); ?>
<?php echo $this->form->renderField('attachment'); ?> <?php echo $this->form->renderField('attachment'); ?>
<?php echo $this->form->renderField('image'); ?> <?php echo $this->form->renderField('image'); ?>

View File

@ -7,7 +7,7 @@
<author>Tommaso Cippitelli</author> <author>Tommaso Cippitelli</author>
<authorEmail>tommaso.cippitelli@protocollicreativi.it</authorEmail> <authorEmail>tommaso.cippitelli@protocollicreativi.it</authorEmail>
<authorUrl>http://</authorUrl> <authorUrl>http://</authorUrl>
<version>CVS: 1.0.0</version> <version>CVS: 1.1.0</version>
<description></description> <description></description>
<namespace path="src">Pcrt\Component\Circolari</namespace> <namespace path="src">Pcrt\Component\Circolari</namespace>