Mapas Culturais > Guia do Desenvolvedor


O intuito deste documento é dar uma visão panorâmica da arquitetura e funcionamento do Mapas Culturais para quem quiser colaborar no desenvolvimento da plataforma. Este documento está ainda incompleto e em constante desenvolvimento.

Branches e desenvolvimento

O desenvolvimento do Mapas Culturais segue o padrão Git Workflow, com as seguintes branches principais:

Branch develop:

Branch master:

Para fazer uma nova instalação, utilize o release (tag) mais atual.

Requisitos

Bibliotecas PHP Utilizadas

Ver arquivo composer.json - Slim - Microframework em cima do qual foi escrita a classe App do MapasCulturais. - Doctrine/ORM - ORM utilizado para o mapeamento das entidades. - Opauth/OpenId - Utilizado para autenticação via OpenId. - respect/validation - Utilizado para as validações das propriedades e metadados das entidades. - smottt/wideimage - Utilizado para transformar imagens (criar thumbnails, por exemplo). - phpunit/phpunit - Utilizado para testes. - creof/doctrine2-spatial - Faz o mapeamento de várias procedures do PostGIS para o doctrine. - mustache/mustache - Utilizado para renderizar alguns templates. - phpoffice/phpword - Utilizado para criar .docs ou .xls onde necessário. - michelf/php-markdown - Utilizado para renderizar os markdowns das páginas - pomo/pomo - Biblioteca Gettext para PHP, usado para internacionalização

Bibliotecas Javascript Utilizadas

Ver bibliotecas javascript utilizadas no tema.

Arquivo de Configuração

App

Traits

Os traits ficam no namespace MapasCulturais\Traits e seus arquivos na pasta src/protected/application/lib/MapasCulturais/Traits.

Se houver no nome do trait um prefixo (Entity, Controller ou Repository) significa que este trait só deve ser utilizado em classes que estendam a classe com o nome do prefixo dentro do namespace MapasCulturais (ex: o trait EntityAvatar só deve ser utilizado em classes que estendem a classe MapasCulturais\Entity). Já se não houver um prefixo significa que é um trait genérico e que pode ser utilizado em qualquer classe (exemplos: Singleton e MagicGetter).

Traits Genéricos

Os traits genéricos podem ser usados em qualquer classe do sistema.

Singleton

Implementa o design pattern singleton. É utilizada nas classes App, GuestUser, ApiOutput, Controller entre outras.

MagicGetter

MagicSetter

MagicCallers

Model

As classes de modelo ficam no namespace MapasCulturais\Entities e seus arquivos dentro da pasta src/protected/application/lib/MapasCulturais/Entities.

Estas classes devem estender a classe abstrata MapasCulturais\Entity e usar os Docblock Annotations do Doctrine para fazer o mapeamento com a representação desta entidade no banco de dados (geralmente uma tabela).

Estas podem também usar os traits criados para entidades (os que têm o prefixo Entity no nome, como por exemplo o EntityFiles, que é para ser usado em entidades que têm arquivos anexos).

Classe Entity

A classe abstrata MapasCulturais\Entity é a classe que serve de base para todas as entidades do sistema. Implementa uma série de métodos úteis para, entre outros, verificação de permissões, serialização e validações.

Traits das Entidades

Verificação de Permissões

A verificação das permissões é feita através do método checkPermission, passando como parâmetro para este o nome da ação que você deseja checar se o usuário tem ou não permissão para executar. Este método, por sua vez, chama o método canUser que retornará um booleano true se o usuário pode executar a ação ou false se o usuário não pode executar a ação. Caso o usuário não possa executar a ação, o método checkPermission lançará uma exceção do tipo PermissionDenied.

Método canUser

O método canUser recebe como primeiro parâmetro o nome da ação e opcionalmente, como segundo parâmetro, um usuário. Se nenhum usuário for enviado, será usado o usuário logado ou guest. O retorno desta função é um booleano indicando se o usuário pode ou não executar a ação.

Este método procurará por um método auxiliar chamado canUser acrescido do nome da ação (exemplo: para a ação remove, um método chamado canUserRemove) e caso não ache será usado o método genericPermissionVerification.

No exemplo a seguir dizemos que somente admins podem alterar o status da entidade Exemplo.

class Exemplo extends MapasCulturais\Entity{
    use MapasCulturais\Traits\MagicSetter
    ....
    ....
    protected $_status = 0;

    function setStatus($status){
        $this->checkPermission('modifyStatus');
        $this->_status = $status;
        $this->save();
    }

    protected function canUserModifyStatus($user){
        if($user->is("admin"))
            return true;
        else
            return false;
    }
}

Método genericPermissionVerification

Este método é utilizado sempre que uma checagem de permissão é feita e o método canUser não encontra um método auxiliar com o nome da ação.

O corpo deste método é o seguinte:

protected function genericPermissionVerification($user){
    if($user->is('guest'))
        return false;

    if($user->is('admin'))
        return true;

    if($this->getOwnerUser()->id == $user->id)
        return true;

    if($this->usesAgentRelation() && $this->userHasControl($user))
        return true;

    return false;
}

Validações das Entidades

Controller

View

Temas

Por enquanto ainda não temos resolvida a estrutura para múltiplos temas. O que temos é um tema único dentro da pasta src/protected/application/themes/active, que será modificado para aceitar configurações.

Bibliotecas Javascript utilizadas no tema

Por enquanto ainda não utilizamos um gerenciador de pacotes para as bibliotecas Javascript. Estas ficam na pasta assets/vendor/. - AngularJS - jQuery

theme.php

Este arquivo fica na pasta raiz do tema (src/protected/application/themes/active) e é usado para colocar funções helpers usadas dentro do tema e para estender o sistema utilizando a API de plugins.

Estrutura de pastas do tema

Dentro da pasta raiz do tema - assets/ - aonde deve ficar tudo que é acessível pelo público dentro da url /public do site - css/ - fonts/ - img/ - vendor/ - layouts/ - aonde ficam os layouts do site - parts/ - aonde ficam os template parts utilizados pelo tema - views/ - aonde ficam as visões dos controles - pages/ - aonde ficam os arquivos de páginas

Páginas

As páginas do sistema são arquivos .md (Markdown) salvos dentro da pasta pages/ do tema. Para criar uma nova página basta criar um novo arquivo .md dentro desta pasta. Estes arquivos são renderizadas pela biblioteca PHP Markdown Extra.

Url da página

Para uma página cujo nome de arquivo é nome-da-pagina.md, a url de acesso será http://mapasculturais/page/site/nome-da-pagina/

Título da página

O texto do primeiro h1 do conteúdo da página será utilizado como título da página (tag title). Isto é feito via javascript.

No exemplo a seguir o título da página será Título da Página

# Título da Página

Conteúdo da página ....

O Conteúdo das sidebars estão nos arquivos _right.md e _left.md

Substituindo uma sidebar

Você pode substituir uma sidebar envolvendo o conteúdo que você deseja que substitua o conteúdo padrão com as tags <%left left%> para a sidebar da esquerda e <%right right%> para a sidebar da direita.

No exemplo a seguir substituímos a sidebar da direita por um menu com três links:

<%right 
- [Primeiro link](#primeiro)
- [Segundo link](#segundo)
- [Terceiro link](#terceiro)
right%>

# Título da Página

Conteúdo da página ....

Estendendo uma sidebar

Você pode extender uma sidebar, adicionando conteúdo antes ou depois do conteúdo padrão, colocando um :after ou :before logo depois da tag de abertura.

No exemplo a seguir estendemos a sidebar da esquerda adicionando um menu com 2 links no final da sidebar:

<%left:after
## submenu da página

- [Primeiro Link](#primeiro)
- [Segundo Link](#segundo)
left%>

# Título da Página

Conteúdo da página ....

Layouts

O layout é a "moldura" do conteúdo de uma visão. A estrutura mínima de um layout é a seguinte:

<html>
    <head>
        <title><?php echo isset($entity) ? $this->getTitle($entity) : $this->getTitle() ?></title>
        <?php mapasculturais_head(isset($entity) ? $entity : null); ?>
    </head>
    <body>
        <?php body_header(); ?>
        <?php echo $TEMPLATE_CONTENT; /* aqui entra o conteúdo da view */ ?>
        <?php body_footer(); ?>
    </body>
</html>

Por padrão as visões usam o arquivo de layout default.php, mas você pode definir qual layout elas usarão colocando a seguinte linha na primeira linha do seu arquivo de visão:

$this->layout = 'nome-do-layout'; // não precisa do .php no nome do template

Visões

As visões são chamadas de dentro das actions do controller através do método render, que inclui o layout definido na view, ou do método partial, que não inclui o layout.

Quando a visão é chamada pelo método render, o conteúdo renderizado da visão é guardado na variável $TEMPLATE_CONTENT e enviado para o layout.

Visões das actions single, create e edit

Os arquivos de visão single.php, create.php e edit.php dos controladores agent, space, event e project são, na realidade, o mesmo arquivo. O arquivo real é o single.php e os dois outros são links simbólicos para o primeiro.

Para saber, de dentro de um destes arquivos, em qual action você está, você pode usar a propriedade $this->controller->action:

<?php if($this->controller->action == 'single'): ?>
    <p>você está visualizando a entidade</p>
<?php elseif($this->controller->action == 'edit'): ?>
    <p>você está editando a entidade</p>
<?php else: ?>
    <p>você está criando uma nova entidade<p>
<?php endif; ?>

Se você só deseja saber se está no modo de edição use a função is_editable():

<?php if(is_editable(): ?>
    <p> você está em modo de edição (edit ou create). </p>
<?php else: ?>
    <p> você está somente visualizando a entidade. <p>
<?php endif; ?>

Partes

As partes são blocos de código que podem ser incluídos em diferentes views, layouts ou mesmo dentro de outras partes. Estes blocos de código devem ficar, por padrão, na pasta layouts/parts/ do tema.

Para usar uma parte cujo nome de arquivo é uma-parte.php basta chamar o método part da seguinte forma:

<div> A parte será incluida a seguir: </div>
<?php $this->part('uma-parte'); ?>

Enviando variáveis para dentro das partes

Você pode enviar variáveis para usar dentro das partes. Isto é útil em várias situações, por exemplo quando você quer que uma parte seja usada dentro de um loop e você tem que enviar o item atual do loop para usar dentro da parte.

No exemplo a seguir, passamos uma variável chamada user_name com o valor "Fulano de Tal" para dentro da parte uma-parte.

// dentro de algum arquivo de view, layout ou mesmo outra parte
$this->part('uma-parte', ['user_name' => 'Fulano de Tal']);
<!-- dentro do arquivo layouts/parts/uma-parte.php -->
<span>Nome de usuário: <?php echo $user_name; ?></span>

Assets

Os assets são arquivos estáticos (.css, .js, imagens, etc.) utilizados pelo tema.

Para imprimir a url de um asset use a função $this->asset(). Já se você deseja adicionar um js ou css use as funções $app->enqueueScript() e $app->enqueueStyle().

Método Asset

O Método asset do objeto de função serve para imprimir ou somente retornar a url de um asset. Este método aceita dois parâmetros:

O primeiro, $file, é o caminho do arquivo deseja dentro da pasta assets do tema, como exemplo a string "img/uma-image.jpg".

O Segundo, $print, é opcional e tem como padrão true. Se for passado false a função somente retornará a url, mas não imprimirá nada.

Adicionando uma imagem

O exemplo a seguir usa uma imagem chamada logo.png que está na pasta assets/img/ do tema.

<img src="<?php $this->asset('img/logo.png'); ?>" />

O exemplo a seguir cria um link para o arquivo documento.pdf que está na pasta asset/ do tema.

<a href="<?php $this->asset('documento.pdf'); ?>" >Documento</a>

Método enqueueStyle

Este método é utilizado para adicionar arquivos .css que serão utilizados pela visão, layout ou parte. Este método aceitas 5 parâmetros ($group, $script_name, $script_filename, array $dependences, $media), sendo os dois últimos opcionais.

Há três grupos de estilos no sistema: vendor, que são estilos utilizados pelas bibliotecas, fonts que são as fontes utilizadas, e app, que são os estilos escritos exclusivamente para o tema.

Adicionando um estilo

O exemplo a seguir adiciona um estilo chamado um-estilo.css escrito para a aplicação.

$app->enqueueStyle('app', 'um-estilo', 'css/um-estilo.css');

Método enqueueScript

Este método é utilizado para adicionar arquivos .js que serão utilizados pela visão, layout ou parte. Este método aceitas 4 parâmetros ($group, $script_name, $script_filename, array $dependences), sendo o último opcional.

Há dois grupos de scripts no sistema: vendor, que são as bibliotecas utilizadas, e app, que são os scripts escritos exclusivamente para o tema.

Adicionando um script

O exemplo a seguir adiciona um script chamado um-script.js escrito para a aplicação.

$app->enqueueScript('app', 'um-script', 'js/um-script.js');
Adicionando um script que depende de outro

O exemplo a seguir adiciona uma biblioteca que depende de outra biblioteca.

$app->enqueueScript('vendor', 'jquery-ui-datepicker', '/vendor/jquery-ui.datepicker.js', array('jquery'));
$app->enqueueScript('vendor', 'jquery', '/vendor/jquery/jquery-2.0.3.min.js');

Ordem de impressão das tags de estilos e scripts

Os grupos de estilos e scripts serão impressos na seguinte ordem e dentro dos grupos os estilos/scripts serão ordenados conforme suas dependências: - Estilos do grupo vendor - Estilos do grupo font - Estilos do grupo app - Scripts do grupo vendor - Scripts do grupo app

Variáveis Acessíveis

De dentro dos arquivos das visões (views, layouts e parts) as seguintes variáveis estão acessíveis: - $this - instância da classe MapasCulturais\View. - $this->assetUrl - url dos assets. - $this->baseUrl - url da raiz do site. - $this->controller - o controller que mandou renderizar a visão. - $this->controller->action - a action que mandou renderizar a visão. - $app - instância da classe MapasCulturais\App. - $app->user - o usuário que está vendo o site. Este objeto é uma instância da classe MapasCulturais\Entities\User (se o usuário estiver logado), ou instância da classe MapasCulturais\GuestUser, se o usuário não estiver logado. - $app->user->profile - o agente padrão do usuário. Instância da classe MapasCulturais\Entities\Agent. (somente para usuários logados) - $app->getCurrentSubsite() - Se estiver utilizando o SaaS, retorna a instância do subsite atual - $entity - é a entidade que está sendo visualizada, editada ou criada. (somente para as actions single, edit e create dos controladores das entidades agent, space, project e event. Dentro das partes somente se esta foi enviada) - $app->view - instância da classe MapasCulturais\Theme, que por sua vez herda da classe Slim\View. É inicializado logo no bootstrap do $app, e podemos utilizá-lo também através do método $app->getView().

Este objeto é bastante útil no fluxo do desenvolvimento, pois podemos utilizar várias de suas propriedades para debugar e nos situarmos melhor no contexto em que estamos da aplicação, como: - $app->getView()->_libVersions - Propriedade do tema padrão (BaseV1), mantém um array com os nomes e versões exatas das bibliotecas javascript que o tema adiciona e usa. - $app->getView()->template - Retorna uma string identificando o template que está sendo renderizado naquele momento. Em geral padronizada para "{controller}/{action}" - $app->getView()->getAssetManager() - Nos traz uma instância de MapasCulturais\App\FileSystem contendo informações detalhadas sobre os scripts JS e estilos CSS que foram carregados naquela view através das propriedades _enqueuedScripts e _enqueuedStyles, respectivamente - inclusive separadas pelos grupos app (do próprio Mapas) e vendor (bibliotecas de terceiros).
A propriedade config ainda nos dá, dentre outras informações, o caminho completo do sistema para a pasta pública dos assets. - $app->getView()->bodyClasses - Traz informações sobre o controller e action da requisição, e são utilizadas no atributo class da tag HTML body, possibilitando um maior nível de customização do layout com base na view. - $app->getView()->getTemplatesDirectory() - Informa o path completo da pasta onde estão os templates carregados. - $app->getView()->_dict -> Exibe as strings internacionalizadas que foram carregadas para o tema

Outra propriedade bastante útil do objeto do tema é a jsObject, por sua vez uma instância de ArrayObject . Esta propriedade é manipulada diversas vezes ao longo do lifecycle da aplicação, de modo que seus dados são dinâmicos de acordo com a entidade em questão, além de manterem também chaves com o mesmo valor ao longo das rotas e requisições.

Por exemplo, as seguintes chaves mantém seus valores independentemente das entidades:

 $app->getView()->jsObject['baseURL']
 $app->getView()->jsObject['labels'] 
 $app->getView()->jsObject['mapsDefaults'] 
 $app->getView()->jsObject['routes'] 
 ```
 Já as chaves de *jsObject* `gettext`, `isEditable`, `isSearch`, `request`, `userProfile` e `entity` variam de acordo com o controller e entidade, tornando esse objeto ainda mais flexível para o desenvolvedor.

 Seguindo ainda com o objeto de view, podemos também fazer uso de informações do controller:

 - **$app->getView()->getController()** Retorna o controller da requisição atual, bem como a entidade correspondente ao mesmo (na propriedade *entityClassName*);
 - **$app->getView()->getRequestedEntity()** Traz o registro da entidade correspondente à resposta da requisição.

 Ao utilizarmos este método para uma requisição a `${URLBASE}/oportunidade/43` por exemplo, e esta oportunidade for vinculada a uma entidade Projeto, teremos a instância de id 43 de ProjectOpportunity,
 obviamente com todos seus registros salvos, como data de criação e atualização, tags, nome, descrição, metadados e owner (referente à outra instância de um objeto Agente), dentre outros.

 - **$app->getView()->getController()->getUrlData()** Retorna os parâmetros passados pela URL. Se foram mapeados pelo hook do $app (neste sentido, um hook do Slim Framework), vêm com o nome mapeado. Caso contrário, os parâmetros são trazidos num array em ordem crescente.

 Por exemplo, se mapearmos apenas o $id no hook, utilizando o método acima para a requisição `${URLBASE}/agente/1/outroParam/EmaisOutro/14`, teremos o seguinte retorno:
 ```
 array:4 [▼
   "id" => "1"
   0 => "outroParam"
   1 => "EmaisOutro"
   2 => "14"
 ]
 ```

- **$app->getView()->getController()->getRepository()** - Retorna o objeto repositório da entidade gerenciada pelo Doctrine correspondente àquele controller e view.
A diferença entre utilizar este método ou invocar diretamente `$app->repo(${nome-da-classe-da-entidade})` é que este último retorna o repositório da entidade passada por parâmetro, e não está atrelada ao contexto da requisição, tal qual o primeiro.
    - Além do nome da entidade gerenciada e do próprio entityManager do Doctrine, o objeto repositório traz os metadados da classe, incluindo detalhes como o namespace da entidade, todo o mapeamento que o Doctrine fez de cada atributo, os *callbacks* de lifecycle, nome da tabela correspondente e até mesmo detalhes sobre as constantes, métodos e propriedades. 

### Verificando se um usuário está logado
Para saber se um usuário está logado você pode verificar se o usuário não é *guest*. 
```HTML+PHP
<?php if( ! $app->user->is('guest') ): ?>
    <p>O usuário está logado e o nome do agente padrão dele é <?php echo $app->user->profile->name; ?> ?></p>
<?php else: ?>
    <p>O usuário não está logado</p>
<?php endif; ?>

Exceções

PermissionDenied