mercoledì 21 luglio 2010

Rilasciato lyMediaManagerPlugin 0.5.2

Ho continuato il lavoro estivo sul plugin lyMediaManagerPlugin. La versione 0.5.2 consente la possibilità di effettuare l'upload di un file (immagine o altro) e la creazione di una sottocartella direttamente dalla vista 'a icone'.

Chi avesse avuto voglia di provare la versione precedente può fare l'upgrade semplicemente con il comando

./symfony plugin:upgrade lyMediaManagerPlugin --stability=beta

L'opzione stability è obbligatoria in quanto il plugin è in una versione beta.

L'alternativa come al solito è il checkout dal repository Subversion seguendo le istruzioni presenti nel file README ed online.

Tutto qui. Preferisco fare poche modifiche per volta e rilasciare nuove versioni con una certa frequenza.

martedì 13 luglio 2010

lyMediaManagerPlugin 0.5.1

Ho rilasciato la versione 0.5.1 beta del plugin lyMediaManager. La principale novità è costituita dal task 'synchronize' del tutto analogo a quello presente nel plugin sfAssetsLibrary, che consente di sincronizzare la libreria di media con il contenuto di una cartella sul server. Tra le altre cose questo può essere utile per chi trovi più comodo caricare immagini o altri file via FTP piuttosto che tramite interfaccia web. Sintassi e parametri sono nel file README.

Inoltre ho aggiunto la funzione di paginazione e reso ordinabili per nome o data i file nella visualizzazione a icone. Nella visualizzazione a lista questa funzione era già disponibile, perché fornita dall'admin generator, e attivabile nel modo standard con un click sull'intestazione della colonna.

A parte questo solo aggiustamenti vari.

Nella visualizzazione a icone sono presenti dei collegamenti che aprono i form per la creazione di una sottocartella o l'upload di un file in una pagina separata. Chi ha utilizzato sfAssetsLibrary avrà notato che queste operazioni si compiono senza lasciare la pagina tramite dei form 'a comparsa': è un'interfaccia più immediata che proverò a ricreare anche in lyMediaManager. Prossimamente.

giovedì 8 luglio 2010

Un media manager per Lyra

In un CMS è necessario offrire agli utenti uno strumento per gestire facilmente una libreria di immagini (ma anche altri file o documenti) da inserire negli articoli o comunque utilizzare per il proprio sito.

Per Lyra ho cercato un plugin di symfony che offrisse questo tipo di funzionalità. Quello che mi è piaciuto di più è sfAssetsLibrary che consente la gestione degli upload dei file e la generazione automatica delle miniature delle immagini, ma permette anche di associare informazioni aggiuntive ad ogni file (titolo descrizione, autore, copyright). Solo che è per Propel, mentre a me serviva per Doctrine.

Quindi ho iniziato lo sviluppo di un nuovo plugin lyMediaManager, ispirato a sfAssetsLibrary anche se non un vero e proprio 'porting'. Per quanto sia pensato per essere incluso in Lyra è un plugin standard che può essere installato in qualsiasi applicazione symfony.

Il lavoro da fare è ancora molto, è comunque disponibile una versione beta. Non mi dilungo oltre, il pacchetto include un file README con le istruzioni. Come per ogni altro plugin si può scegliere l'installazione tramite comando symfony 'plugin:install' oppure fare il checkout via SVN direttamente dal repository: la seconda strada permette di avere le ultime novità prima che siano 'impacchettate' nella successiva release, anche se al prezzo di una maggiore instabilità.

Il plugin si trova sul repository ufficiale di symfony, il link per chi vuole darci un'occhiata è questo

www.symfony-project.org/plugins/lyMediaManagerPlugin

martedì 27 aprile 2010

symfony, gestire un profilo utente con sfGuard

Ad oggi la gestione utenti di Lyra è quasi inesistente. Viene utilizzato sfDoctrineGuardPlugin per proteggere con login il backend dell'applicazione, ma esiste di fatto un solo utente, il super-amministratore, creato automaticamente con il caricamento dei dati di esempio (fixtures). Usando le funzioni di sfDoctrineGuardPlugin possono già essere aggiunti nuovi utenti e gruppi, ma non venendo gestiti permessi specifici, ogni nuovo utente può operare senza alcuna restrizione, risultando a tutti gli effetti pratici indistinguibile dal super-amministratore.

Inoltre per ogni utente possono essere salvate solo le informazioni di base gestite dalla classe sfGuardUser: nome utente e password. Questo è il primo aspetto da migliorare con la creazione di un profilo utente che ci consenta di gestire informazioni aggiuntive: nome, cognome ed indirizzo e-mail; almeno per il momento, altri campi potranno essere aggiunti in seguito se necessario.

Ho seguito questa guida pubblicata sul blog ufficiale di symfony.

Per prima cosa si crea nello schema una classe per il profilo utente

config/doctrine/schema.yml

LyraUserProfile:
  tableName: users
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    user_id:
      type: integer(4)
    first_name:
      type: string(80)
    last_name:
      type: string(80)
    email:
      type: string(150)
  relations:
    User:
      class: sfGuardUser
      local: user_id
      foreign: id
      type: one
      foreignType: one
      foreignAlias: Profile
      onDelete: CASCADE

L'impostazione 'onDelete: CASCADE' non è presente nella guida: credo sia una svista perché altrimenti l'esistenza della relazione rende impossibile cancellare un utente dal backend.

Si modificano le fixtures per includere le informazioni del profilo negli utenti creati con i dati di esempio, nel mio caso il super-amministratore

data/fixtures/users.yml

sfGuardUser:
  admin:
    username:       admin
    password:       admin
    is_super_admin: true

#User profile

LyraUserProfile:
  p_admin:
    first_name: Lyra
    last_name: Administrator
    email: lyra@localhost
    User: admin

Si ricostruiscono le classi del modello e si ricaricano i dati di esempio con

./symfony doctrine:build --all --and-load

La sintassi del comando è quella da usare in symfony 1.4.

A questo punto si deve personalizzare il form per la creazione e modifica di un utente nel backend in modo da poter inserire le informazioni del profilo. La guida citata all'inizio dice di copiare in lib/form/doctrine il file sfGuardUserAdminForm.class.php che si trova nella cartella plugins/sfDoctrineGuardPlugin/lib/form/doctrine e procedere con la personalizzazione della copia. In questo modo un successivo aggiornamento del plugin non sovrascriverà le nostre modifiche. Il problema è che facendo in questo modo a me la cosa non funziona, in particolare si verificano errori nei test funzionali. Infatti durante i test l'autoload carica la classe sfGuardUserAdminForm originale (quella nella cartella del plugin) e non la copia modificata nella cartella lib/form/docrine del progetto.

Non sono stato tanto ad indagare ed ho preso una strada leggermente diversa: ho creato una classe LyraUserAdminForm derivata da sfGuardUserAdminForm

lib/form/doctrine/LyraUserAdminForm.class.php

class LyraUserAdminForm extends sfGuardUserAdminForm
{
  public function configure()
  {
    parent::configure();

    $profileForm = new LyraUserProfileForm($this->object->Profile);
    unset($profileForm['id'], $profileForm['user_id']);
    $this->embedForm('user_profile', $profileForm);
    $this->widgetSchema['user_profile']->setLabel(false);
  }
}

Il form con le informazioni del profilo viene inizializzato con l'oggetto ottenuto dalla relazione definita nello schema (Profile, foreignAlias della relazione User di LyraUserProfile) e poi incluso nel form principale con embedForm().

Bisogna poi modificare generator.yml perché venga utilizzata la nostra classe al posto di sfGuardUserAdminForm e per visualizzare in un 'pannello' il form embedded (user_profile) con le informazioni del profilo.

apps/backend/modules/sfGuardUser/config/generator.yml
...
     form:
        class: LyraUserAdminForm
        display:
          "NONE": [username, password, password_again]
          PANEL_PROFILE: [user_profile]
          PANEL_PERMISSIONS: [is_active, is_super_admin, groups_list, permissions_list]
...

Dopo aver modificato questo file ho dovuto anche rimuovere (in realtà ne ho commentato il contenuto) il file generator.yml originale installato dal plugin in plugins/sfDoctrineGuardPlugin/modules/sfGuardUser/config/. Diversamente è impossibile personalizzare alcune opzioni di configurazione. Mi sembra di ricordare qualche post nel forum di symfony dove veniva fatto notare questo problema. La cosa seccante è la necessità di ripetere l'operazione ad ogni aggiornamento del plugin, ma soluzioni alternative non ne ho trovate.

Alla fine ho creato i file di lingua per la traduzione dell'interfaccia. Tutto il lavoro fatto è visibile nel log delle modifiche della revisione 52.

lunedì 26 aprile 2010

Lyra, ultimi sviluppi

Lo sviluppo di Lyra continua anche se ultimamente non ho ho avuto molto tempo di scrivere sul blog. Faccio quindi un breve punto della situazione riassumendo le modifiche apportate dalle ultime revisioni. Il codice come al solito può essere consultato nel repository su Google Code.

Modifiche alla gestione metatags dei contenuti

La gestione dei metatags (meta-title, meta-description, meta-keywords, meta-robots) dei contenuti avviene tramite un modulo apposito (classe LyraMetatagsForm) incorporato nel modulo di inserimento / modifica del contenuto. Sono stati inoltre creati due parametri di configurazione globale per aggiungere un prefisso o un suffisso al meta-titolo della pagina. Questo permette ad esempio di avere titoli delle pagine del tipo

Titolo articolo | Nome sito

oppure

Nome sito | Titolo articolo

Dove 'Nome sito |', '| Nome sito' sono il valore di un parametro di configurazione e possono essere quindi impostati a piacere. (revisione 43).

Modifiche al layout del backend

Ho fatto qualche modifica per migliorare l'aspetto e rendere più pulita e ordinata l'area di amministrazione (revisioni 44 e 47).

Ottimizzazione liste di selezione etichette

La generazione delle liste di selezione delle etichette da assegnare ad un contenuto è stata ottimizzata e richiede adesso una sola query anche quando ci sono più cataloghi utilizzabili per il tipo di contenuto (revisione 45). Inoltre la gestione delle liste di selezione delle etichette è stata spostata dalla classe LyraArticleForm a LyraContentForm, in quanto questa parte dovrà essere utilizzata anche per i tipi di contenuto diversi da 'Article', che ancora non esistono (revisone 46).

Modifica alla gestione dei parametri di configurazione

La gestione dei parametri di configurazione globali e dei tipi di contenuto è stata ristrutturata. Il valore di un parametro di configurazione non viene più letto con il metodo getCfg() di LyraContent, ma viene utilizzata una classe apposita (LyraConfig) istanziata nelle azioni e passata al template. Mi sembra questa una soluzione più pulita e più aderente al pattern MVC dell'implementazione precedente (revisione 48).

Aggiornamento del framework

Lyra utilizza adesso l'ultima versione di symfony (1.4.4). Anche il plugin sfDoctrineGuardPlugin è stato aggiornato. (revisioni 49, 50, 51).

martedì 9 febbraio 2010

Lyra, tipo di contenuto 'pagina'

Chi ha familiarità con Wordpress o Drupal conosce la differenza tra articolo (story in Drupal) e pagina. Un concetto grosso modo analogo è presente in Joomla che distingue i contenuti standard dai contenuti statici (Joomla 1.0) o dai contenuti non categorizzati (Joomla 1.5). Chi non sapesse invece di cosa si sta parlando troverà un'ottima spiegazione in questo articolo di Html.it.

La ragione di questa distinzione è semplice: in molti casi (in un blog, ma anche nei siti che pubblicano notizie come quotidiani online e simili) è utile gestire in modo diverso gli articoli veri e propri che contengono informazioni 'datate' e i contenuti che contengono informazioni meno sensibili al decorrere del tempo e quindi in un certo senso più 'statiche': ad esempio una pagina con il profilo dell'autore del blog, la descrizione o l'organico dell'azienda.

Anche il modo in cui sono presentate queste due categorie di contenuti è spesso diverso: di regola un articolo contiene l'indicazione della data di pubblicazione e dell'autore, mentre queste informazioni non sono mostrate in una pagina; gli articoli sono presentati in ordine cronologico in prima pagina o nella pagina della rispettiva categoria, mentre le pagine sono accessibili da link o menù; anche il formato delle URL può essere diverso.

E se volessimo creare qualcosa del genere in Lyra? Arrivati a questo punto non è difficile farlo: basterà creare un nuovo tipo di contenuto, page, tanto per non stare ad inventarci nomi diversi, anch'esso gestito dallo stesso modello e modulo che già utilizziamo per il contenuto article.

In effetti le differenze tra articoli e pagine non sono tanto rilevanti da giustificare la creazione di modelli separati: ci basta 'clonare' un tipo di contenuto esistente e sfruttare i parametri di configurazione (vedere Lyra, parametri di configurazione articoli) per differenziare l'apetto degli articoli e delle pagine.

Per prima cosa ho inserito un nuovo record nella tabella content_types nel file con i dati di esempio.

data/fixtures/content_types.yml

LyraContentType:
  ...
  Pagina:
    name: Page
    type: page
    module: article
    model: LyraArticle
    ...

Ho modificato il file routing.yml del backend perché a questo punto fa comodo avere l'ID del tipo di contenuto nella rotta

apps/backend/config/routing.yml
...
lyra_article:
  class: sfDoctrineRouteCollection
  options:
    model:                LyraArticle
    module:               article
    prefix_path:          /article/:ctype_id
    column:               id
    object_actions:
      publish: get
      unpublish: get
      feature: get
      unfeature: get
    with_wildcard_routes: true
...

e pure quello del frontend per differenziare il formato delle URL di articoli e pagine

apps/frontend/config/routing.yml

page_show:
  url: /:slug.html
  class: sfDoctrineRoute
  options:
    type: object
    model: LyraArticle
    method: findItem
  param:
    module: article
    action: show
  requirements:
    sf_method: [get]

I parametri del nuovo tipo di contenuto possono essere impostati dal backend, ad esempio possiamo modificare l'aspetto del contenuto pagina in modo da non mostrare autore e data di creazione e non consentire i commenti.

Altre modifiche si possono vedere nel log della revisione 41. Il tutto richiede un po' di lavoro manuale, ma è possibile che in futuro queste operazioni possano essere gestite direttamente dal backend.

mercoledì 3 febbraio 2010

Lyra, parametri di configurazione globali

Dopo la gestione dei parametri di configurazione a livello di contenuto e di tipo di contenuto, mi è sembrato logico creare una funzione che consenta di impostare dal backend dei parametri di configurazione globali.

Le definizioni di tali parametri, analogamente a quanto avviene per i parametri dei contenuti, è contenuta in un file YAML (config/lyra_params.yml). A partire da questo file viene generato il form (LyraSettingsForm) per l'inserimento e la modifica dei valori che vengono salvati nel database (tabella settings, modello LyraSettings). Infine la classe LyraCfg serve a leggere il valore di un parametro.

Come si vede dalla figura attualmente ci sono solo due parametri di configurazione, quando ne serviranno altri sarà sufficiente aggiungere le definizioni in lyra_params.yml.

Tanto per fare un esempio vediamo come si utilizza il parametro Moderazione commenti.

config/lyra_params.yml

...
moderate_comments:
    type: list
    choices: [moderate_all, moderate_no_auth, moderate_none]
    default: moderate_all
...

Il tipo list indica che il parametro può assumere un valore tra quelli indicati in choices.

  • moderate_all: tutti i commenti sono sempre moderati. Compaiono sul sito solo previa approvazione dell'amministratore.
  • moderate_no_auth: solo i commenti degli utenti anonimi (cioè che visitano il sito senza aver fatto il login) sono moderati.
  • moderate_none: nessuna moderazione, ogni commento è immediatamente visibile sul sito.

Da notare che gli identificativi dei parametri come i valori delle opzioni sono sempre in inglese nel file YAML, ma vengono tradotti nei file di lingua (nella figura all'inizio si vedono infatti in italiano).

In questa parte della funzione updateObject() di LyraCommentForm, a seconda del valore scelto per il parametro si imposta o meno a true il campo is_active (pubblicato) del commento appena inserito.

lib/form/doctrine/LyraCommentsForm.class.php
...
      switch(LyraCfg::get('moderate_comments')) {
        case 'moderate_none':
          $publish = true;
          break;
        case 'moderate_no_auth':
          $publish = $user_auth;
          break;
        case 'moderate_all':
        default:
          $publish = false;
          break;
      }
      $item->setIsActive($publish);
...

Accenno solo brevemente alle altre modifiche contenute nelle revisioni 37 e 38: i file contenenti le definizioni dei parametri di configurazione dei contenuti / tipi di contenuto non sono più nella cartella config dell'applicazione, ma nella cartella config del modulo (backend). Ad esempio

config/params/article.yml

non esiste più, l'equivalente è ora

apps/backend/modules/article/config/params.yml

Il codice nel metodo configure() di LyraArticleForm che gestiva la configurazione dei form embedded usati per visualizzare e modificare i parametri di configurazione dell'articolo è stato spostato in LyraContentForm. Questa parte sarà standard e comune a tutti i tipi di contenuto, inserendola nella classe base eviteremo future duplicazioni di codice. Il tutto sarà più chiaro in seguito appena saranno creati altri tipi di contenuto in aggiunta ad article.

Sono stati aggiunti nuovi campi a LyraContentType: type, model, plugin. A cosa servono si avrà modo di vederlo nelle prossime puntate. Per il momento è tutto.

giovedì 14 gennaio 2010

Lyra, parametri di configurazione articoli

Fino a questo punto l'aspetto dei contenuti è determinato esclusivamente dal layout e dai template creati nelle diverse action. Si presenta quindi l'esigenza di consentire all'utente un certo grado di personalizzazione dei contenuti attraverso opzioni o parametri di configurazione accessibili dal backend dell'applicazione.

Questo tipo di funzionalità è presente in molti cms e se ne capisce facilmente la ragione. Solo per fare un esempio, costringere l'utente alla modifica dei template per impostare un diverso formato della data di visualizzazione degli articoli sarebbe scomodo e quasi sicuramente causa di errori.

Vediamo quindi come verranno gestiti i parametri di configurazione dei contenuti in Lyra. Ogni parametro potrà essere impostato a livello di singolo articolo e di tipo di contenuto: se il valore di un parametro non è impostato in modo specifico per un singolo articolo, sarà utilizzato il valore del parametro impostato per il tipo di contenuto.

Facciamo un esempio.

1) parametri del tipo di contenuto article

2) parametri di un singolo articolo.

Con riguardo all'articolo in questione, il parametro 'Mostra data' è impostato su No, tutti gli altri prendono i valori predefiniti impostati sul tipo di contenuto (nell'esempio saranno tutti ).

Per vedere come i valori dei parametri di configurazione vengono letti dall'applicazione basta dare uno sguardo ad uno dei template del modulo article, ad esempio questo

apps/frontend/modules/article/templates/_byline.php

<div class="article-byline">
  <?php if($item->getCfg('show_author')): ?>
  <span class="article-author">
    <?php echo __('ARTICLE_WRITTEN_BY', array('%author%'=> $item->getArticleCreatedBy()->getUsername()));?>
  </span>
  <?php endif; ?>
  <?php if($item->getCfg('show_date')): ?>
  <span class="article-date">
    <?php echo __('ARTICLE_WRITTEN_ON', array('%date%'=>format_date($item->getCreatedAt(),'dd MMMM, yyyy'))); ?>
  </span>
  <?php endif; ?>
</div>

Il metodo getCfg() è implementato in una nuova classe LyraContent. Si sfrutta l'ereditarietà di Doctrine per derivare LyraArticle da questa classe base

config/doctrine/schema.yml

LyraContent:
  columns:
    ctype_id:
      type: integer(4)
    params:
      type: clob

LyraArticle:
  tableName: articles
  inheritance:
    extends: LyraContent
    type: concrete
...

Anche le classi che gestiranno gli altri tipi di contenuto saranno derivate da LyraContent in modo che le funzionalità relative ai parametri di configurazione siano comuni a tutti i contenuti gestiti dal cms.

L'impostazione dei parametri avviene nel backend tramite i form da cui provengono le schermate riportate all'inizio di questo articolo. La classe relativa è LyraParamsForm: i widget per inserire il valore di ogni parametro sono generati dinamicamente a partire dalle informazioni contenuto in un file YAML. Per quanto riguarda il tipo di contenuto article abbiamo config/params/article.yml, file analoghi saranno creati per ciascun tipo di contenuto. Per creare un nuovo parametro basta aggiungerne la definizione in questo file.

Analogamente a quanto visto per il form con le liste di selezione delle etichette, anche il form con i parametri di configurazione non viene utilizzato da solo, ma incorporato (embedded) in altri form: vedere le istruzioni nei metodi configure() di BackendLyraArticleForm e LyraContentTypeForm.

Nomino infine la classe LyraParams che contiene funzioni per la lettura/scrittura dei parametri di configurazione da e nel database (campo params di LyraContent e LyraContentType).

Per il momento sono disponibili un numero limitato di opzioni di configurazione, altre ne saranno aggiunte via via che servono, ho scritto anche i test funzionali per testare questa parte. Tutto questo è disponibile nella revisione 35.