freakstyle

~~~ Really only another WordPress ? ~~~

MODX ~ gestione documenti correlati

Sarebbe comodo poter disporre in MODx di un meccanismo che permetta di correlare tra di loro le pagine, al di la` della struttura propria dell’albero di risorse, come avverrebbe per esempio per la gestione di un catalogo prodotti.
MODx (almeno nell’attuale “stable” 1.0.3) non lo permette, ma noi possiamo aggiungere questa funzionalitá.
Prima di cominciare con le modifiche vere e proprie peró, occorre fare un attimo il punto della situazione per avere le idee chiare di cosa faremo.
Intanto occorre decidere se la correlazione tra i documenti deve essere simmetrica o meno, nel senso:
se é simmetrica (la indico con CS d’ora in poi) allora una volta che io correlo il documento A al documento B, é come se avessi correlato B ad A, quindi non c’é un ‘correlato’ e un ‘correlatore’;
se non é simmetrica (questa con CA) esiste invece tale relazione, e quindi é un rapporto in un solo verso;
scegliere una o l’altra dipende da cosa effettivamente ci serve e dalle conseguenze sulle modifiche che occorre apportare al codice di MODx.
La cosa + importante é certamente quello che ci serve a noi,…. ma, non mi viene in mente nessuna situazione reale in cui la CA sarebbe davvero necessaria, quindi opto per la versione simmetrica. Alla fine del post sará anche chiaro che optare per la CA avrebbe delle ripercussioni importanti e non trascurabili su tutto il lavoretto che stiamo per fare.
Per non modificare la struttura di MODx, creeremo una nuova tabella modx_site_content_correlated con due chiavi esterne a documenti che assieme formano la chiave primaria. Qui chiaramente salveremo le correlazioni e andremo a verificare se sia il caso di cancellare ogni volta che verrá rimosso un elemento (occorrerá anche decidere come fare ció in relazione ai doc cancellati ma non eliminati); la gestione avverrá in modo simile al comando “sposta” aggiungendo la nuova operazione dal menu contestuale.
Forse mi sono perso troppo in chiacchiere, quindi procediamo!!!

Prima di tutto aggiungiamo al db la tabella di cui abbiamo parlato (se utilizzate un prefix non default per le tabelle modificate la query qui sotto….gelato!):

CREATE TABLE `modx_site_content_correlated` (
`fk_doc1` INT( 10 ) NOT NULL ,
`fk_doc2` INT( 10 ) NOT NULL ,
PRIMARY KEY ( `fk_doc1` , `fk_doc2` )
) ENGINE = MYISAM ;

ora nel menu contestuale aggiungiamo la voce “Gestione Correlati”, quindi modifichiamo /manager/frames/tree.php (esattamente come fatto in questo post precedente):

nella seconda parte del body, all`interno del div#nameHolder ci sono varie chiamate ad una funzione (constructLink) intervallate da dei div.separator; questa funzione stampa un elemento nel menu contestuale. Io la inserisco come ultima opzione.
Il primo parametro di constructLink è forse il più importante, perchè poi invidua esattamente l`azione da intraprendere….. quindi aggiungiamo la chiamata che ci interessa, modificando anche il secondo e terzo parametro come segue:

<?php
constructLink
(
13,
$_style["icons_correlate_resource"],
$_lang["correlate_resource"],
$modx->hasPermission('save_document')
);
?>


….sempre all`interno dello stesso file occorre fare una piccola modifica allo switch contenuto all`interno della funzione javascript `menuHandler`, che indicano al file /manager/index.php quale azione intraprendere (sarà il prossimo file da modificare). Inseriamo quindi il nuovo caso:

case 13 : // correlation (works as move)
  top.main.document.location.href="index.php?a=51b&id=" + itemToChange;
  break;

inserendo quindi come identificatore dell`azione la stringa ’51b’.

Ora modifichiamo il file /manager/index.php in modo che risponda correttamente al nuovo caso in cui $_GET['a'] valga esattamente ’51b’; qui le azioni dirette vengono eseguite tramite inclusione di un file presente in /manager/processors mentre quelle come questa che hanno delle opzioni di scelta includono in genere un file in actions/, quindi ne includiamo uno; il passo successivo sarà crearlo.
Quindi, all’interno dello switch($action) aggiungiamo il caso :

<?php
case "51b" :
  
// get the move action
  
include_once "header.inc.php";
  include_once 
"actions/correlate_document.dynamic.php";
  include_once 
"footer.inc.php";
break;
?>


questo fara` si che nel contenuto venga caricata una pagina (che dobbiamo ancora creare) che ci permette di visualizzare, eliminare e aggiungere dei documenti correlati. Ma prima di creare questo file facciamo un piccolo passo indietro e mettiamo a posto le nuove etichette e icone che abbbiamo dato per esistenti:
- per le etichette modifichiamo /manager/includes/lang/italian.inc.php (o tutte le lingue che ci interessano) aggiungendo:

<?php
$_lang
["correlate_resource"] = 'Gestione correlati';
$_lang['correlate_resource_title'] = 'Correlaziome documenti';
$_lang['correlate_resource_message'] = 'Potete Correlare  una risorsa a qualunque
 altra selezionandone una o piu` nella struttura ad albero.'
;
$_lang['correlate_resource_new']='Nuove risorse correlate';
$_lang['correlate_resource_remove']='Elimina correlato';
$_lang['new_correlated']='Nuove risorse correlate';
$_lang['resource_to_be_correlated']='Risorsa da correlare';
$_lang['unable_set_correlated']='Impossibile settare correlato';
?>


…alcune di queste non sono ancora state utilizzate, ma presto ci serviranno.
- e per le icone (io ho utilizzato questa immagine che va inserita in /manager/media/style/MODxCarbon/images/icons/ ) modifichiamo /manager/media/style/MODxCarbon/style.php aggiungendo:

<?php
$_style
["icons_correlate_resource"] = $style_path.'icons/sort.png';
?>


Ora possiamo procedere e creare il nuovo file /manager/actions/correlate_document.dynamic.php contenente il codice che segue:

<?php
if(IN_MANAGER_MODE!="true")
  die(
"<b>INCLUDE_ORDERING_ERROR</b>".
       
"<br /><br />".
       
"Please use the MODx Content Manager instead of accessing this file directly."
  
);
if(!
$modx->hasPermission('save_document')) {
  
$e->setError(3);
  
$e->dumpError();
}
if( isset(
$_REQUEST['id']) ) {
  
$id intval($_REQUEST['id']);
} else {
  
$e->setError(2);
  
$e->dumpError();
}
// check permissions on the document
include_once "./processors/user_documents_permissions.class.php";
$udperms = new udperms();
$udperms->user $modx->getLoginUserID();
$udperms->document $id;
$udperms->role $_SESSION['mgrRole'];
//
if(!$udperms->checkPermissions()) {
  
?><br /><br />
  <div class="sectionHeader"><?php echo $_lang['access_permissions']; ?></div>
  <div class="sectionBody">
    <p><?php echo $_lang['access_permission_denied']; ?></p>
    <?php
    
include("footer.inc.php");
    exit;
}
?>
<script type="text/javascript">
parent.tree.ca = "correlate";
var correlateds = new Object();
<?php
$corr 
$modx->getCorrelated($id);
foreach(
$corr as $row){
  if(
$row['fk2'] == $id){?>
    correlateds[<?php echo $row['fk1'?>] = '<?php echo $row['tit1'?>';
  <?php
  
}
  if(
$row['fk1'] == $id){?>
    correlateds[<?php echo $row['fk2'?>] = '<?php echo $row['tit2'?>';
  <?php
  
}
}
?>
function setCorrelateValue(pId, pName) {
  if (checkParentChildRelation(pId, pName)) {
    correlateds[pId] = pName;
    //update form hidden
    showCorrelated();
  }
}
//
function showCorrelated(){
  var res = '';
  var hid = new Array();
  for(var i in correlateds){
    res += "<br/>&raquo; <b>" +
      i +
      "</b> (" +
      correlateds[i] +
      ") <a href='javascript:removeCorrelated("+
      i+
      ");' title='<?php echo $_lang['correlate_resource_remove']; ?>'>X</a>";
    hid.push(i);
  }
  document.newdocumentcorrelated.new_correlateds.value = hid.join('_');
  document.getElementById('parentName').innerHTML = res;
}
//
function removeCorrelated(pId){
  for(var i in correlateds)
    if(i == pId){ delete correlateds[i];}
    showCorrelated();
}
//
function checkParentChildRelation(pId, pName) {
  var presente = false;
  for(var i in correlateds)
    if(correlateds[i] == pId)
      presente = true;
  return (pId!=<?php echo $id?> && !presente);
}
</script>
<h1><?php echo $_lang['correlate_resource_title']; ?></h1>
<div id="actions">
  <ul class="actionButtons">
    <li>
      <a href="#" onclick="document.newdocumentcorrelated.submit();">
        <img src="<?php echo $_style["icons_save"?>" />
        <?php echo $_lang['save'?>
      </a>
    </li>
    <li>
      <a href="#" onclick="documentDirty=false;<?php
      
echo $id==?
      
"document.location.href='index.php?a=2';"
      
:
      
"document.location.href='index.php?a=3&id=$id';";
      
?>">
      <img src="<?php echo $_style["icons_cancel"?>" />
      <?php echo $_lang['cancel']?>
      </a>
    </li>
  </ul>
</div>
<div class="sectionHeader"><?php echo $_lang['correlate_resource_title']; ?></div>
<div class="sectionBody">
  <?php echo $_lang['correlate_resource_message']; ?><p />
  <form method="post" action="index.php" name='newdocumentcorrelated'>
    <input type="hidden" name="a" value="52b">
    <input type="hidden" name="id" value="<?php echo $id?>">
    <input type="hidden" name="idshow" value="<?php echo $id?>">
    <?php echo $_lang['resource_to_be_correlated']; ?>: <b><?php echo $id?></b><br />
    <span id="parentName" class="warning"><?php echo $_lang['correlate_resource_new']; ?></span><br />
    <input type="hidden" name="new_correlateds" value="" class="inputBox"><br />
    <input type='save' value="Correlate" style="display:none">
  </form>
</div>
<script type="text/javascript">
  //mostro i correlati al caricamento
  showCorrelated();
</script>

Il valore impostato alla variabile js parent.tree.ca conta molto perche` permette che al click su di un elemento dell’albero
questo venga aggiunto tra i documenti da correlare, …..ma manca qualcosa, infatti il file tree.php non sa ancora come trattare questa opzione; aggiungiamo quindi al file /manager/frames/tree.php all`interno della funzione javascript treeAction il codice che segue:

if(ca=="correlate") {
  try {
    parent.main.setCorrelateValue(id, name);
  } catch(oException) {
    alert('<?php echo $_lang['unable_set_correlated']; ?>');
  }
}

Come potete notare non siamo ancora arrivati al dunque visto che ci appoggiamo su una funzione che non esiste ancora ( $modx->getCorrelated($id) ) e ci manda da scrivere ancora il file processor che salva quanto ci serve.
Quindi per prima cosa modifichiamo il file /manager/includes/document.parser.class.inc.php e piu’ precisamente aggiungiamo alla classe DocumentParser i tre metodi che seguono:

<?php
//ottiene i documenti (id e nome) correlati a un documento passato
// come array
function getCorrelated($id){
  
$sql '
    SELECT corr.fk_doc1 as fk1,
                corr.fk_doc2 as fk2,
                content1.pagetitle as tit1,
                content2.pagetitle as tit2
      FROM '
.$this->getFullTableName("site_content_correlated") .' as corr
LEFT JOIN '
.$this->getFullTableName("site_content") .' as content1
          ON (corr.fk_doc1 = content1.id AND  (corr.fk_doc1 = '
.$id.' OR corr.fk_doc2 = '.$id.'))
LEFT JOIN '
.$this->getFullTableName("site_content") .' as content2
          ON (corr.fk_doc2 = content2.id AND (corr.fk_doc1 = '
.$id.' OR corr.fk_doc2 = '.$id.'))';
  
$return = array();
  
$result $this->dbQuery($sql);
  while(
$tmpRow$this->fetchRow($result)){
    
$return[] = $tmpRow;
  }
  return 
$return;
}
//
//salva i correlati di un documento passato 
// arr e' un array contenente gli id dei doc da correlare
function setCorrelated($id$arr){
  
$vals = array();
  foreach(
$arr as $k){
    
array_push($vals'('.$id.','.$k.')');
  }
 
$sql '
   INSERT
       INTO '
.$this->getFullTableName("site_content_correlated") .'
   VALUES '
.implode(','$vals).'
  '
;
  return 
$this->dbQuery($sql);
}
//
// elimina i correlati al documento $id
function clearCorrelated($id){
  
$sql '
    DELETE 
      FROM '
.$this->getFullTableName("site_content_correlated") .'
   WHERE fk_doc1 = '
.$id.' OR fk_doc2 = '.$id;
  return 
$this->dbQuery($sql);
}
?>


occorre usare una di queste funzioni anche nel caso in cui un documento venga cancellato (anche se non ancora eliminato definitivamente), quindi apriamo il file /manager/processors/delete_content.processor.php e all’interno del blocco dell` “else” che segue il commento
//ok, ‘delete’ the document.
(in 1.0.3 e’ alla linea 102) inseriamo proprio all’inizio la chiamata il codice

<?php
  $modx
->clearCorrelated($id);
?>


questo ci assicura che alla rimozione di un documento verranno rimossi dalla nostra nuova tabelle dei correlati tutti i record che coinvolgono il documenti che stiamo cancellando.

Nel file correlate_document.dynamic.php che abbiamo creato da poco, abbiamo inserito nel form un campo nascosto di nome ‘a’ cheoramai sapete bene cosa serve e gli abbiamo dato il valore ’52b’…. quindi, come al solito, apriamo il file /manager/index.php e nello switch($action) aggiungiamo il caso:

<?php
case "52b" :
  
// process correation
  
include_once "processors/correlate_document.processor.php";
break;
?>


ora non ci resta che creare il file /manager/processors/correlate_document.processor.php e fargli fare esattamente cio’ che ci serve, potendo utilizzare in ogni caso le tre funzioni che abbiamo aggiunto; nel contenuto inseriamo il codice che segue:

<?php
if(IN_MANAGER_MODE!="true")
  die(
"<b>INCLUDE_ORDERING_ERROR</b>".
       
"<br /><br />Please use the MODx Content Manager ".
       
"instead of accessing this file directly.");
if(!
$modx->hasPermission('edit_document')) {
  
$e->setError(3);
  
$e->dumpError();
}
// ok, two things to check.
// first, document cannot be moved to itself
// second, new parent must be a folder. If not, set it to folder.
if($_REQUEST['id']=="") {
  
$e->setError(601);
  
$e->dumpError();
}
$id $_REQUEST['id'];
//
//azzero tutte le correlazioni con questo prodotto
// ... tanto le ha gia caricate quelle attive e le ha anche gia messe nell`array js
$modx->clearCorrelated($id);
//
mysql_query($sqlzero);
//
$new_correlateds explode('_'$_REQUEST['new_correlateds']);
if(
$new_correlateds[0]!=='')
  
$modx->setCorrelated($id$new_correlateds);
//
// check user has permission to move document to chosen location
if ($use_udperms == 1) {
  if (
$oldparent != $newParentID) {
    include_once 
MODX_MANAGER_PATH .
      
"processors/user_documents_permissions.class.php";
    
$udperms = new udperms();
    
$udperms->user $modx->getLoginUserID();
    
$udperms->document $newParentID;
    
$udperms->role $_SESSION['mgrRole'];
    
//
    
if (!$udperms->checkPermissions()) {
      include (
"header.inc.php");
      
?><script type="text/javascript">parent.tree.ca = '';</script>
      <br /><br />
      <div class="sectionHeader">
        <?php echo $_lang['access_permissions']; ?>
      </div>
      <div class="sectionBody">
        <p><?php echo $_lang['access_permission_parent_denied']; ?></p>
        <?php
          
include ("footer.inc.php");
          exit;
    }
  }
}
//
// empty cache & sync site
include_once "cache_sync.class.processor.php";
$sync = new synccache();
$sync->setCachepath(MODX_BASE_PATH "assets/cache/");
$sync->setReport(false);
$sync->emptyCache(); // first empty the cache
$header="Location: index.php?r=1&id=$id&a=7";
header($header);
?>


Tutto qui, ciao a tutti!
Federico

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)

Categorised as: modx, php


4 Comments

  1. Andrea says:

    Ciao, ho letto tutti i tuoi post su modx, davvero interessanti complimenti! (finalmente anche in Italia si stà diffondendo)

    Dal mio punto di vista emerge un unico problema..modx come gran parte dei software opensource viene costantemente aggiorato quindi penso che le modifiche sul codice debbano essere molto meno invasive.
    Magari si può cercare di usare il più possibile classi esterne o nel caso di modx sviluppare dei “moduli”, in questo modo si potrebbe stare in linea con i rilasci ufficiali senza perdere le nuove funzionalità sviluppate.

    Ciao,
    Andrea

  2. microcipcip says:

    Ciao,
    prima di tutto complimenti per gli articoli. Quello per salvare le pagine con gli shortcut è molto utile. Sono contento che la comunità italiana si stia allargando, facciamo di tutto per diffondere questo fantastico CMS! Anche io sto scrivendo dei tutorial per MODx nel mio sito, jertix.org.

    Comunque, non ho capito bene cosa intendi per correlazione pagine. Non si puo usare Ditto o Wayfinder per questo?

    • admin says:

      Ciao microcipcip, grazie per i complimenti.
      Se non sbaglio entrambi gli snippet di cui parli permettono grandi manovre
      partendo da un padre specifico nell’albero dei documenti, e filtrare tra i documenti contenuti
      in base a dei parametri passati allo snippet, per poi rielaborarli su template.
      Si potrebbe effettivamente utilizzare uno dei due snippet relegando l’utente a creare i correlati di un documento
      in una specifica posizione dell’albero (il documento stesso, che diverrebbe “cartella”),
      e quindi sfruttare la sola gerarchia….. è vero
      ma…., se fosse necessario correlare uno stesso documento a più “padri”? … occorrerebbe duplicarlo;
      e per scorrelarne uno occorrerebbe eliminarlo o almeno spostarlo. Questione di gusti, ma a me non piace. :D

      Con la modifichetta che ho fatto io, qualunque utente può amministrarsi i documenti correlati, senza ridondanza,
      in modo totalmente indipendente da dove essi siano posizionati all’interno della struttura.
      Dall’altra, chi crea il sito, può utilizzare dove occorre le tre funzioni che ho aggiunto.
      Non so come, ma mi hai fatto venire in mente che devo fare una modifica: impedire la correlazione
      di un documento con un documento cartella!… Grazie!
      Se non ho capito il tuo dubbio oppure non hai capito cosa intendo, fammelo sapere! :D
      ciao
      Federico

  3. microcipcip says:

    Grazie mille per la spiegazione. Ora e tutto piu chiaro :)
    Comunque, vorrei parlarti di una cosa, potresti contattarmi tramite email?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>