Docker para desarrollo Yii2 en MacOS

En el post anterior vimos cómo crear un entorno de desarrollo para Drupal8 usando Docker.

Pero, ¿y si quiero usar otro framework de PHP, por ejemplo Yii2, en mi OSX? Es muy sencillo.

Instalación del DOCKER stack (docker4php)

docker4php miguelm$ cd Sites/testyii2
docker4php miguelm$ git clone https://github.com/wodby/docker4php
docker4php miguelm$ cd docker4php

Editar .env y dejarlo como sigue:

### Documentation available at https://docs.wodby.com/stacks/php/local
### Changelog can be found at https://github.com/wodby/docker4php/releases
### Images tags format explained at https://github.com/wodby/docker4php#images-tags

### PROJECT SETTINGS

PROJECT_NAME=unizaryii
PROJECT_BASE_URL=unizaryii.docker.localhost

DB_NAME=php
DB_USER=php
DB_PASSWORD=php
DB_ROOT_PASSWORD=password
DB_HOST=mariadb
DB_DRIVER=mysql

### --- PHP ----

#PHP_TAG=7.2-dev-4.8.2
#PHP_TAG=7.1-dev-4.8.2
#PHP_TAG=5.6-dev-4.8.2
PHP_TAG=7.2-dev-macos-4.8.2
#PHP_TAG=7.1-dev-macos-4.8.2
#PHP_TAG=5.6-dev-macos-4.8.2

### --- NGINX ----

NGINX_TAG=1.15-5.0.17
#NGINX_TAG=1.14-5.0.17

### --- NODE ---

NODE_TAG=10-0.9.0
#NODE_TAG=8-0.9.0
#NODE_TAG=6-0.9.0

### --- MARIADB ----

MARIADB_TAG=10.1-3.3.11
#MARIADB_TAG=10.2-3.3.11
#MARIADB_TAG=10.3-3.3.11

### --- POSTGRESQL ----

POSTGRES_TAG=11-1.5.0
#POSTGRES_TAG=10-1.5.0
#POSTGRES_TAG=9.6-1.5.0
#POSTGRES_TAG=9.5-1.5.0
#POSTGRES_TAG=9.4-1.5.0
#POSTGRES_TAG=9.3-1.5.0

### --- REDIS ---

REDIS_TAG=4-3.0.2
#REDIS_TAG=5-3.0.2

### --- ELASTICSEARCH ---

ELASTICSEARCH_TAG=6.3-3.0.2
#ELASTICSEARCH_TAG=6.2-3.0.2
#ELASTICSEARCH_TAG=6.1-3.0.2
#ELASTICSEARCH_TAG=6.0-3.0.2
#ELASTICSEARCH_TAG=5.6-3.0.2
#ELASTICSEARCH_TAG=5.5-3.0.2
#ELASTICSEARCH_TAG=5.4-3.0.2

### --- KIBANA ---

KIBANA_TAG=6.3-3.0.2
#KIBANA_TAG=6.2-3.0.2
#KIBANA_TAG=6.1-3.0.2
#KIBANA_TAG=6.0-3.0.2
#KIBANA_TAG=5.6-3.0.2
#KIBANA_TAG=5.5-3.0.2
#KIBANA_TAG=5.4-3.0.2

### --- SOLR ---

SOLR_TAG=7.4-3.0.6
#SOLR_TAG=7.3-3.0.6
#SOLR_TAG=7.2-3.0.6
#SOLR_TAG=7.1-3.0.6
#SOLR_TAG=6.6-3.0.6
#SOLR_TAG=5.5-3.0.6

### OTHERS

ADMINER_TAG=4.6-3.2.0
APACHE_TAG=2.4-4.0.2
ATHENAPDF_TAG=2.10.0
MEMCACHED_TAG=1-2.2.1
RSYSLOG_TAG=latest
VARNISH_TAG=4.1-3.0.10
WEBGRIND_TAG=1.5-1.6.2
OPENSMTPD_TAG=6.0-1.4.0
XHPROF_TAG=1.0.2

Compilamos y levantamos…

docker4php miguelm$ docker-compose up -d

En el fichero docker-compose.yml la variable NGINX_SERVER_ROOT tiene el valor /var/www/html/public
Así que creamos el directorio public y un fichero index.php para probar si todo ha ido bien…

docker4php miguelm$ mkdir public
docker4php miguelm$ echo "" > public/index.php

Lo siguiente será editar (como root) el fichero /etc/hosts de la máquina anfitrión y añadir algunas entradas que nos serán útiles:

127.0.0.1 unizaryii.docker.localhost
127.0.0.1 pma.unizaryii.docker.localhost

Y ya podemos ir a nuestro navegador en la máquina anfitrión: http://unizaryii.docker.localhost:8000/ y comprobar que todo funciona.

Instalación de Yii2 y creación de proyecto

Ahora que ya hemos comprobado que el setup funciona, vamos a instalar Yii2.

docker4php miguelm$ git clone https://github.com/yiisoft/yii2 _host-volumes/yii2
docker4php miguelm$ mv _host-volumes/yii2 .
docker4php miguelm$ rm -Rf _host-volumes

Y vamos a crear nuestro primer proyecto Yii2:

# eliminamos el directorio "public" completo
docker4php miguelm$ rm -Rf public
# creamos un nuevo proyecto en la carpeta "demo" 
docker4php miguelm$ composer create-project yiisoft/yii2-app-basic demo

Lo siguiente será editar el fichero docker-compose.yml para indicar el nuevo valor de NGINX_SERVER_ROOT, que deberá ser:

NGINX_SERVER_ROOT: /var/www/html/demo/web

Paramos y arrancamos los dockers con la nueva configuración:

docker4php miguelm$ docker-compose stop
docker4php miguelm$ docker-compose up -d

Ya tenemos nuestro proyecto Yii2 listo…

¿Y si en lugar de NGINX quiero utilizar Apache como servidor web?

Muy sencillo. Editamos el archivo docker-compose.yml y comentamos la sección de NGINX y descomentamos la sección de Apache. El fichero docker-compose.yml quedaría así:

version: "3"

services:
  mariadb:
    image: wodby/mariadb:$MARIADB_TAG
    container_name: "${PROJECT_NAME}_mariadb"
    stop_grace_period: 30s
    environment:
      MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD
      MYSQL_DATABASE: $DB_NAME
      MYSQL_USER: $DB_USER
      MYSQL_PASSWORD: $DB_PASSWORD
#    volumes:
#      - ./mariadb-init:/docker-entrypoint-initdb.d # Place init .sql file(s) here.
#      - /path/to/mariadb/data/on/host:/var/lib/mysql # I want to manage volumes manually.

#  postgres:
#    image: wodby/postgres:$POSTGRES_TAG
#    container_name: "${PROJECT_NAME}_postgres"
#    stop_grace_period: 30s
#    environment:
#      POSTGRES_PASSWORD: $DB_PASSWORD
#      POSTGRES_DB: $DB_NAME
#      POSTGRES_USER: $DB_USER
#    volumes:
#      - ./postgres-init:/docker-entrypoint-initdb.d # Place init file(s) here.
#      - /path/to/postgres/data/on/host:/var/lib/postgresql/data # I want to manage volumes manually.

  php:
    image: wodby/php:$PHP_TAG
    container_name: "${PROJECT_NAME}_php"
    environment:
      PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
      DB_HOST: $DB_HOST
      DB_USER: $DB_USER
      DB_PASSWORD: $DB_PASSWORD
      DB_NAME: $DB_NAME
## Read instructions at https://wodby.com/stacks/php/docs/local/xdebug/
#      PHP_XDEBUG: 1
#      PHP_XDEBUG_DEFAULT_ENABLE: 1
#      PHP_XDEBUG_REMOTE_CONNECT_BACK: 0
#      PHP_IDE_CONFIG: serverName=my-ide
#      PHP_XDEBUG_REMOTE_HOST: 172.17.0.1 # Linux
#      PHP_XDEBUG_REMOTE_HOST: 10.254.254.254 # macOS
#      PHP_XDEBUG_REMOTE_HOST: 10.0.75.1 # Windows
    volumes:
      - ./:/var/www/html
## For macOS users (https://wodby.com/stacks/php/docs/local/docker-for-mac/)
#      - ./:/var/www/html:cached # User-guided caching
#      - docker-sync:/var/www/html # Docker-sync
## For XHProf and Xdebug profiler traces
#      - files:/mnt/files

#  nginx:
#    image: wodby/nginx:$NGINX_TAG
#    container_name: "${PROJECT_NAME}_nginx"
#    depends_on:
#      - php
#    environment:
#      NGINX_STATIC_OPEN_FILE_CACHE: "off"
#      NGINX_ERROR_LOG_LEVEL: debug
#      NGINX_BACKEND_HOST: php
#      NGINX_VHOST_PRESET: php
#      NGINX_SERVER_ROOT: /var/www/html/demo/web
#    volumes:
#      - ./:/var/www/html
# Options for macOS users (https://wodby.com/stacks/php/docs/local/docker-for-mac/)
#      - ./:/var/www/html:cached # User-guided caching
#      - docker-sync:/var/www/html # Docker-sync
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_nginx'
#      - 'traefik.port=80'
#      - 'traefik.frontend.rule=Host:${PROJECT_BASE_URL}'

  apache:
    image: wodby/apache:$APACHE_TAG
    container_name: "${PROJECT_NAME}_apache"
    depends_on:
      - php
    environment:
      APACHE_LOG_LEVEL: debug
      APACHE_BACKEND_HOST: php
      APACHE_VHOST_PRESET: php
      APACHE_DOCUMENT_ROOT: /var/www/html/demo/web
    volumes:
      - ./:/var/www/html
## For macOS users (https://wodby.com/stacks/php/docs/local/docker-for-mac/)
##      - ./:/var/www/html:cached # User-guided caching
##      - docker-sync:/var/www/html # Docker-sync
    labels:
      - 'traefik.backend=${PROJECT_NAME}_apache'
      - 'traefik.port=80'
      - 'traefik.frontend.rule=Host:${PROJECT_BASE_URL}'

  mailhog:
    image: mailhog/mailhog
    container_name: "${PROJECT_NAME}_mailhog"
    labels:
      - 'traefik.backend=${PROJECT_NAME}_mailhog'
      - 'traefik.port=8025'
      - 'traefik.frontend.rule=Host:mailhog.${PROJECT_BASE_URL}'

#  varnish:
#    image: wodby/varnish:$VARNISH_TAG
#    container_name: "${PROJECT_NAME}_varnish"
#    depends_on:
#      - nginx
#    environment:
#      VARNISH_SECRET: secret
#      VARNISH_BACKEND_HOST: nginx
#      VARNISH_BACKEND_PORT: 80
#      VARNISH_PURGE_EXTERNAL_REQUEST_HEADER: X-Real-IP
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_varnish'
#      - 'traefik.port=6081'
#      - 'traefik.frontend.rule=Host:varnish.${PROJECT_BASE_URL}'

#  redis:
#    container_name: "${PROJECT_NAME}_redis"
#    image: wodby/redis:$REDIS_TAG

#  adminer:
#    container_name: "${PROJECT_NAME}_adminer"
#    image: wodby/adminer:$ADMINER_TAG
#    environment:
## For PostgreSQL:
##      ADMINER_DEFAULT_DB_DRIVER: pgsql
#      ADMINER_DEFAULT_DB_HOST: $DB_HOST
#      ADMINER_DEFAULT_DB_NAME: $DB_NAME
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_adminer'
#      - 'traefik.port=9000'
#      - 'traefik.frontend.rule=Host:adminer.${PROJECT_BASE_URL}'

#  pma:
#    image: phpmyadmin/phpmyadmin
#    container_name: "${PROJECT_NAME}_pma"
#    environment:
#      PMA_HOST: $DB_HOST
#      PMA_USER: $DB_USER
#      PMA_PASSWORD: $DB_PASSWORD
#      PHP_UPLOAD_MAX_FILESIZE: 1G
#      PHP_MAX_INPUT_VARS: 1G
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_pma'
#      - 'traefik.port=80'
#      - 'traefik.frontend.rule=Host:pma.${PROJECT_BASE_URL}'

#  solr:
#    image: wodby/solr:$SOLR_TAG
#    container_name: "${PROJECT_NAME}_solr"
#    environment:
#      SOLR_HEAP: 1024m
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_solr'
#      - 'traefik.port=8983'
#      - 'traefik.frontend.rule=Host:solr.${PROJECT_BASE_URL}'

#  elasticsearch:
#    image: wodby/elasticsearch:$ELASTICSEARCH_TAG
#    environment:
#      ES_JAVA_OPTS: "-Xms500m -Xmx500m"
#    ulimits:
#      memlock:
#        soft: -1
#        hard: -1

#  kibana:
#    image: wodby/kibana:$KIBANA_TAG
#    depends_on:
#      - elasticsearch
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_kibana'
#      - 'traefik.port=5601'
#      - 'traefik.frontend.rule=Host:kibana.php.docker.localhost'

#  memcached:
#    container_name: "${PROJECT_NAME}_memcached"
#    image: wodby/memcached:$MEMCACHED_TAG

#  rsyslog:
#    container_name: "${PROJECT_NAME}_rsyslog"
#    image: wodby/rsyslog:$RSYSLOG_TAG

#  athenapdf:
#    image: arachnysdocker/athenapdf-service:$ATHENAPDF_TAG
#    container_name: "${PROJECT_NAME}_athenapdf"
#    environment:
#      WEAVER_AUTH_KEY: weaver-auth-key
#      WEAVER_ATHENA_CMD: "athenapdf -S"
#      WEAVER_MAX_WORKERS: 10
#      WEAVER_MAX_CONVERSION_QUEUE: 50
#      WEAVER_WORKER_TIMEOUT: 90
#      WEAVER_CONVERSION_FALLBACK: "false"

#  node:
#    image: wodby/node:$NODE_TAG
#    container_name: "${PROJECT_NAME}_node"
#    working_dir: /app
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_node'
#      - 'traefik.port=3000'
#      - 'traefik.frontend.rule=Host:front.${PROJECT_BASE_URL}'
#    expose:
#      - "3000"
#    volumes:
#      - ./path/to/your/single-page-app:/app
#    command: sh -c 'npm install && npm run start'

#  blackfire:
#    image: blackfire/blackfire
#    environment:
#      BLACKFIRE_SERVER_ID: XXXXX
#      BLACKFIRE_SERVER_TOKEN: YYYYY

#  webgrind:
#    image: wodby/webgrind:$WEBGRIND_TAG
#    environment:
#      WEBGRIND_PROFILER_DIR: /mnt/files/xdebug/profiler
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_webgrind'
#      - 'traefik.port=8080'
#      - 'traefik.frontend.rule=Host:webgrind.php.docker.localhost'
#    volumes:
#      - files:/mnt/files

#  opensmtpd:
#    container_name: "${PROJECT_NAME}_opensmtpd"
#    image: wodby/opensmtpd:$OPENSMTPD_TAG

#  xhprof:
#    image: wodby/xhprof:$XHPROF_TAG
#    restart: always
#    volumes:
#      - files:/mnt/files
#    labels:
#      - 'traefik.backend=${PROJECT_NAME}_xhprof'
#      - 'traefik.port=8080'
#      - 'traefik.frontend.rule=Host:xhprof.${PROJECT_BASE_URL}'

  portainer:
    image: portainer/portainer
    container_name: "${PROJECT_NAME}_portainer"
    command: --no-auth -H unix:///var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - 'traefik.backend=${PROJECT_NAME}_portainer'
      - 'traefik.port=9000'
      - 'traefik.frontend.rule=Host:portainer.${PROJECT_BASE_URL}'

  traefik:
    image: traefik
    container_name: "${PROJECT_NAME}_traefik"
    command: -c /dev/null --web --docker --logLevel=INFO
    ports:
      - '8000:80'
#      - '8080:8080' # Dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

#volumes:
## Docker-sync for macOS users
#  docker-sync:
#    external: true
## For Xdebug profiler
#  files:

Después simplemente:

docker4php miguelm$ docker-compose up -d --remove-orphans

Widget para wordpress: mostrar los twitts favoritos de tu cuenta twitter

Basándome en elTwitter Widget para WordPress de Sean Spalding, he modificado ligeramente el código para que muestre únicamente los twitts favoritos de un determinado usuario.

El fuente es éste:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php
/*
Plugin Name: Twitter Widget Favoritos
Plugin URI: http://leccionespracticas.com/php/widget-para-wordpress-mostrar-los-twitts-favoritos-de-tu-cuenta-twitter/
Description: Muestra favoritos de twitter de un usuario (usa Javascript <a href="http://twitter.com/badges/which_badge">Twitter 'badge'</a>)
Version: 1.0.0
Author: Miguel Martin
Author URI: http://www.leccionespracticas.com
License: GPL
Basado en: Twitter Widget by Sean Spalding - http://seanys.com/2007/10/12/twitter-wordpress-widget/
 
This software comes without any warranty, express or otherwise, and if it
breaks your blog or results in your cat being shaved, it's not my fault.
 
*/
 
function widget_Twidget_Favoritos_init() {
 
	if ( !function_exists('register_sidebar_widget') )
		return;
 
	function widget_Twidget_Favoritos($args) {
 
		// "$args is an array of strings that help widgets to conform to
		// the active theme: before_widget, before_title, after_widget,
		// and after_title are the array keys." - These are set up by the theme
		extract($args);
 
		// These are our own options
		$options = get_option('widget_Twidget');
		$account = $options['account'];  // Your Twitter account name
		$title = $options['title'];  // Title in sidebar for widget
		$show = $options['show'];  // # of Updates to show
 
        // Output
		echo $before_widget ;
 
		// start
		//echo '<div id="twitter_div">'
        //      .$before_title.$title.$after_title;
		echo $before_title.$title.$after_title.'<div id="twitter_div">';
		echo '<ul id="twitter_update_list"></ul></div>
		      <script type="text/javascript" src="http://twitter.com/javascripts/blogger.js"></script>';
		// Con esta linea buscamos TODOS los twitts de el usuario identificado por $account
		/*echo '<script type="text/javascript" src="http://twitter.com/statuses/user_timeline/'.$account.'.json?callback=twitterCallback2&amp;count='.$show.'"></script>';*/
 
		// Con esta linea buscamos los favoritos del usuario identificado por $account
		// http://api.twitter.com/1/favorites/bibliouz.json?callback=twitterCallback2&count=5
		echo '<script type="text/javascript" src="http://api.twitter.com/1/favorites/'.$account.'.json?callback=twitterCallback2&amp;count='.$show.'"></script>';
 
		// Con esta otra linea se filtra por $account y por $hashtag
		// http://search.twitter.com/search.json?q=%23HASHTAG+from:USERNAME
		// similar a (parámetro rpp final es el número de tweets qse muestran
		// http://search.twitter.com/search?q=&ands=&phrase=&ors=&nots=&tag=bibliotecas&lang=all&from=bibliouz&to=&ref=&near=&within=15&units=mi&since=&until=&rpp=5
        //$hashtag = '%23'.'listado';
		/*echo '<script type="text/javascript" src="http://search.twitter.com/search.json?q=listado+from:bibliouz"></script>';*/
 
 
 
		// echo widget closing tag
		echo $after_widget;
	}
 
	// Settings form
	function widget_Twidget_Favoritos_control() {
 
		// Get options
		$options = get_option('widget_Twidget_Favoritos');
		// options exist? if not set defaults
		if ( !is_array($options) )
			$options = array('account'=>'seanys', 'title'=>'Twitter Updates', 'show'=>'5');
 
        // form posted?
		if ( $_POST['Twitter-submit'] ) {
 
			// Remember to sanitize and format use input appropriately.
			$options['account'] = strip_tags(stripslashes($_POST['Twitter-account']));
			$options['title'] = strip_tags(stripslashes($_POST['Twitter-title']));
			$options['show'] = strip_tags(stripslashes($_POST['Twitter-show']));
			update_option('widget_Twidget_Favoritos', $options);
		}
 
		// Get options for form fields to show
		$account = htmlspecialchars($options['account'], ENT_QUOTES);
		$title = htmlspecialchars($options['title'], ENT_QUOTES);
		$show = htmlspecialchars($options['show'], ENT_QUOTES);
 
		// The form fields
		echo '<p style="text-align:right;">
				<label for="Twitter-account">' . __('Account:') . '
				<input style="width: 200px;" id="Twitter-account" name="Twitter-account" type="text" value="'.$account.'" />
				</label></p>';
		echo '<p style="text-align:right;">
				<label for="Twitter-title">' . __('Title:') . '
				<input style="width: 200px;" id="Twitter-title" name="Twitter-title" type="text" value="'.$title.'" />
				</label></p>';
		echo '<p style="text-align:right;">
				<label for="Twitter-show">' . __('Show:') . '
				<input style="width: 200px;" id="Twitter-show" name="Twitter-show" type="text" value="'.$show.'" />
				</label></p>';
		echo '<input type="hidden" id="Twitter-submit" name="Twitter-submit" value="1" />';
	}
 
 
	// Register widget for use
	register_sidebar_widget(array('Twitter-favoritos', 'widgets'), 'widget_Twidget_Favoritos');
 
	// Register settings for use, 300x200 pixel form
	register_widget_control(array('Twitter-favoritos', 'widgets'), 'widget_Twidget_control', 300, 200);
}
 
// Run code and init
add_action('widgets_init', 'widget_Twidget_Favoritos_init');
 
?>

Las instrucciones de instalación son de lo más simple:

1. Copiar el archivo superior (twitter-widget-favoritos.php) en /wp-content/plugins/widgets (si no existe la carpeta, la crearemos).
2. Activar el plugin en el menú plugins de wordpress
3. Añadir el widget a la barra lateral en el menú widgets de wordpress y rellenar el menú con el id del usuario (por ejemplo, bibliouz).

Lo he testeado y funciona a la perfección con el tema WP Comfy en wordpress 3.0.4

Podéis consultar La API de favoritos de twitter y la del statuses/user_timeline o a API de búsquedas para más detalles.

Si queréis saber más sobre la creación de widgets para wordpress podéis hacerlo en este sencillo manual

Saber la URL actual desde php (o “dónde estás”) [resuelto] + consejillos SEO

¿Cómo saber la URL en la que “estás” con PHP?

Echa un vistazo a este código (pruébalo en tu server):

$host = $_SERVER['HTTP_HOST'];
$self = $_SERVER['PHP_SELF'];
$query = !empty($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : null;
$url = !empty($query) ? "http://$host$self?$query" : "http://$host$self";
echo $url;

¿Para qué quiero saber la URL en la que estoy?

Motivos de SEO
Imagina que diseñas una web y la cabecera (la llamaré header.php) es común a todas tus páginas.

Algo asi:

<!-- esto es header.php -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>miguel martin | fotografía de moda y retrato en Zaragoza</title>
<link rel="stylesheet" type="text/css" href="css/styles.css" />
<!-- Muestro imágenes, cabecera o lo que sea -->

Después, desde cada página de tu web (las llamaré index1.phpindexN.php) haces un

<!-- esto es indexI.php -->
<?php
require_once('header.php');
# o un include('header.php');
?>

De forma que, escribiendo el código de header.php una única vez, éste se “empotre” en todas tus páginas. Hasta aqui, perfecto. Pero como algunos sabréis, google penaliza aquéllos sitios que considera con información duplicada…y la duplicidad de títulos.

¿Cómo puedo entonces saber dónde estoy desde el fichero header.php para que la información del título cambie en cada página?

Pues con algo asi…

<!-- esto es header.php -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 
<?php
/* ¿Dónde demonios estoy? $_SERVER['PHP_SELF']; me dice la parte que me interesa... */
$self = $_SERVER['PHP_SELF'];
 
if (strpos($self,"beauty")) { // si la cadena "self" contiene la palabra "beauty" entonces quiero que mi título sea...
 $self = "Beauty portfolio |";
}
// y bla, bla, bla
else if (strpos($self,"fashion")) { $self = "Fashion portfolio |";}
else if (strpos($self,"sports")) { $self = "Sports portfolio |";}
else if (strpos($self,"beauty")) { $self = "Glamour portfolio |";}
else if (strpos($self,"contact")) { $self = "Contacto |";}
else if (strpos($self,"about")) { $self = "About |";}
else { $self = "";} 
 
?>
<title>miguel martin | <?php echo $self; ?> fotografía de moda y retrato en Zaragoza</title>
<link rel="stylesheet" type="text/css" href="css/styles.css" />

Creo que no hacen falta explicaciones adicionales. ¿No?

Ah! Hace algún tiempo escribí un artículo similar a este pero para joomla, por si os es de utilidad.

Joomla templating – know where you are (full article view or article-listing) [SOLVED]

Some time ago I talked about how to know if you are in the frontpage or not

But you might want to know (for instance, for plugin displaying) if you are viewing a full article or just a listing of articles (with just a preview of the articles’ content).

How do you do this? You must use the JRequest::getVar function as in:

if (JRequest::getVar('view')=='article'){
    // then you are displaying a full article
    // do whatever you want in full article visualization...
}
else{
  // stuff to be displayed in the rest of the pages...
}

You could also use JRequest::getvar('option', '') or JRequest::getCmd( 'option' )to know if you are, for instance, in com_content or in any other module as in:

   <?php if((JRequest::getCmd( 'view' ) == 'article') or (JRequest::getCmd( 'option' ) == 'com_twc4j')) :?>

With this lines you could hack some plugins (like, for example, addthis plugin for joomla) to display just as you want. Take a look at this self-explained code:

function getButtonCode(&$params) {
	if($_REQUEST['view']=="frontpage") return ''; //we are in the frontpage, so return an empty string
	if (JRequest::getVar('view')!='article') return ''; //we are not showing an article, so return an empty string
 
        // Here we know that we are displaying an article... so I want to show addthis code.
        $code = $this->params->get('code');
        if( empty($code) )
            $code = '<br/><a href="http://www.addthis.com/bookmark.php?v=250" onmouseover="return addthis_open(this, \'\', \'[URL]\', \'[TITLE]\')" onmouseout="addthis_close()" onclick="return addthis_sendto()"><img src="http://s7.addthis.com/static/btn/lg-share-en.gif" width="125" height="16" alt="Bookmark and Share" style="border:0"/></a><script type="text/javascript" src="http://s7.addthis.com/js/250/addthis_widget.js?pub=xa-4a3f0b283a364b52"></script><br/>';
        return $code;
}

Thanks a lot to shockM user of joomlaspanish.org forums for the explanation! (Read full post in spanish here).

Montar unidad remota con SAMBA en linux RedHat5 (desde PHP)

Estos días pasados he estado peleándome con lo siguiente: necesitaba que un programa en PHP (ejecutado en máquina llamada RH5, que corre sobre un Red Hat 5) fuera capaz de montar una unidad de cd/dvd remota (más en concreto, de un equipo local Windows XP SP3).

Os comento, un poco por encima, los pasos a seguir para conseguir esto:

  1. Compartir la unidad del equipo windows con nombre de usuario apache (para ello el usuario apache debe existir en el sistema W’s y en este ejemplo, tendrá contraseña=apache). Supongamos que la máquina Windows tiene ip=IPW y el nombre del recurso compartido es E.
  2. Verificar que tenemos el cliente samba instalado en la máquina RedHat5. Para ello podemos comprobar, una vez realizado el paso (1), que desde línea de comandos podemos montar la unidad remota, en, por ejemplo /mnt/miunidadremota (evidentemente, este directorio debe existir en nuestra máquina RH5). Ejecutamos:

    /bin/mount -t cifs //IPW/E /mnt/miunidadremota -o user=apache,pass=apache,uid=apache,gid=apache
    ls /mnt/miunidadremota

    Una vez ejecutados los dos comandos de arriba deberíamos ver los ficheros del cd. Si no lo vemos, hay algo mal en los puntos (1) y (2). Revísalos.

  3. Habilitar en /etc/sudoers que el usuario apache pueda montar y desmontar la unidad remota E (Si no sabes qué es sudoers o mount o para qué se utiliza, googlea man sudo y man moun).

    Evidentemente, el usuario apache debe existir en el sistema RH5. Para comprobar que existe, ejecuta:

    more /etc/passwd | grep apache

    Y deberías ver algo asi:

    apache:x:48:48:Apache:/var/www:/bin/bash


    Nota que el usuario apache tiene shell! (/bin/bash, para más señas).

    Continuamos editando /etc/sudoers. Lo puedes hacer con el comando visudo, o directamente editando /etc/sudoers (este último método no lo recomiendo). Una vez editado, añade al final las siguientes líneas:

    apache ALL = NOPASSWD: /bin/mount
    apache ALL = NOPASSWD: /bin/umount
    apache ALL = /sbin/mount.cifs
    apache ALL = /sbin/umount.cifs

  4. A continuación, comprueba si tienes activado SELinux. En mi caso está desactivado. Si sigues todos los pasos restantes y al final no te funciona, chequea /var/messages para ver qué está pasando…y seguramente encontrarás que SELinux te está impidiendo realizar el montado de la unidad remota…
  5. Haz un more /etc/sudores | grep requiretty
    comprueba que esa línea está comentada, esto es:
    #Defaults requiretty.
    Si la tienes descomentada no funcionará.
  6. Crea tu programa en PHP para montar la unidad remota! 😉
    En mi caso es algo asi (las líneas 8 y 10 son las que importan):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    include("functions.php");
    include("config.php");
    echo "Librerias cargadas...<br />";
     
    if (isset($ipCD)){ // es un rec externo... montarlo!
     
      echo "Montando unidad remota...";
      $mountlike = 'sudo /bin/mount -t cifs //'.$ipCD.'/'.$unidadCD.' '.$rutaEntrada.' -o user='.$userCD.',pass='.$passCD.',uid=apache,gid=apache';
      echo $mountlike.'<br />';
      exec($mountlike, $out, $retval);
    }

  7. Esto es todo amigos!
    Si consideras que se puede hacer mejor, o ves algún fallo que yo no veo… (dejemos la seguridad al margen 😎 ), deja un comentario.

Añadir meta-información a wordpress

Cuando un motor de búsqueda indexa tu sitio web recopila información del título, cabecera, contenido y meta-etiquetas (como descripción y palabras clave). Gracias a esta información tu página será mostrada en una determinada posición de los resultados de búsqueda ofrecidos por un motor. A esta posición se la conoce como ranking.

WordPress, en su instalación por defecto, no incluye meta información, por lo que nos veremos obligados a añadir esta información manualmente, cambiando el código fuente del theme que tengamos instalado. También podemos añadir esta meta información instalando plugins específicos.

¿Qué son los meta-tags?

La palabra meta significa información acerca de. Las meta-etiquetas se concibieron inicialmente para proporcionar información detallada acerca de un sitio web. La información en meta-etiquetas incluye el autor de la página web, las palabras clave, la descripción del sitio web, el tipo de documento, copyright, etc

Un ejemplo de meta-información de descripción es:

Algunas de las meta-etiquetas más comunes son:

Y algunas de las etiquetas de meta información más comunes son:

<meta http-equiv="author" content="Miguel Martin" />
<meta http-equiv="contact" content="admin@leccionespracticas.com" />
<meta name="copyright" content="Copyright (c)2009-2013 Miguel Martin. All Rights Reserved." />
<meta name="description" content="lecciones practicas - aprende informatica" />
<meta name="keywords" content="lecciones practicas, leccionespracticas, cds invenio, repositorios, seo, sef, posicionamiento, google, wordpress, joomla, php, python, preservacion" />

El significado de cada una de estas etiquetas es fácilmente deducible, por lo que no me extenderé en este punto 😉

¿Son los meta-tags necesarios todavía?

Es una buena pregunta. Hace algunos años rellenar de forma correcta esta información era fundamental. A día de hoy algunos motores de búsqueda no utilizan esta información debido a que, históricamente, se ha abusado en la utilización de esta información a cargo de algunos webmasters. De hecho, las meta-etiquetas pueden NO reflejar el contenido de tu sitio web…

De todos modos, sigue siendo una buena práctica utilizar estos pequeños fragmentos de información, siempre que sean utilizados de forma correcta, para completar información de tu sitio web. Algunos motores de búsqueda utilizan la meta-etiqueta keywords (palabras clave) para categorizar y asignar ranking a tu sitio web. Estos motores comparan tus palabras clave con el contenido de tu página y premian a aquéllos sitios en los que las palabras clave coinciden con el tipo de contenido mostrado. Por tanto, sí es importante añadir, al menos, la etiqueta de meta-keywords.

Aunque no te engañes, a día de hoy el elemento diferenciador de cara al posicionamiento es el contenido de tu sitio, es decir, la información en sí misma.

¿Cómo añado meta-tags a mi blog en WordPress

Para añadir meta información a tu sitio web, simplemente edita el archivo header.php del theme que estés utilizando.

A modo de ejemplo puedes ver parte de mi archivo header.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" <?php language_attributes(); ?>>
<head profile="http://gmpg.org/xfn/11">
<meta http-equiv="Content-Type" content="<?php bloginfo('html_type'); ?>; charset=<?php bloginfo('charset'); ?>" />
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<title><?php if (is_home () ) { bloginfo('name'); } elseif ( is_category() ) { single_cat_title(); echo ' - ' ; bloginfo('name'); }
 elseif (is_single() ) { single_post_title(); }
 elseif (is_page() ) { bloginfo('name'); echo ': '; single_post_title(); }
 else { wp_title('',true); } ?></title>
 
<meta http-equiv="author" content="Miguel Martin" />
<meta http-equiv="contact" content="admin@leccionespracticas.com" />
<meta name="copyright" content="Copyright (c)2009-2013 Miguel Martin. All Rights Reserved." />
<meta name="description" content="lecciones practicas - aprende informatica" />
<meta name="keywords" content="lecciones practicas, leccionespracticas, cds invenio, repositorios, seo, sef, posicionamiento, google, wordpress, joomla, php, python, preservacion" />
 
 
<script type="text/javascript" src="<?php bloginfo('template_url'); ?>/script.js"></script>
<link rel="stylesheet" href="<?php bloginfo('stylesheet_url'); ?>" type="text/css" media="screen" />

A estas alturas te estarás preguntando ¿y la meta-información dinámica? Con las líneas que hemos añadido anteriormente, ésta información se mostrará en todas las páginas de tu sitio web. Al colocalas en header.php la información no cambiará, será estática.

De cara a añadir información dinámica, una solución simple pasa por utilizar editar header.php y añadir las siguientes líneas:

<meta name="description" content="<?php if ( is_single() ) {
        single_post_title('', true); 
    } else {
        bloginfo('name'); echo " - "; bloginfo('description');
    }
    ?>" />

Estas líneas hacen que, si estamos ante un único post se muestre el título del mismo, mientras que si estamos ante un conjunto de posts, se muestre el nombre del blog y su descripción.

Otra solución es utilizar alguno de los múltiples plugins de WordPress.

Por ejemplo, puedes usar head space 2, un plugin que sugiere qué meta-keywords añadir a cada post, qué descripción darle, etc.

¿Te interesa el tema? Puede seguir informándote en los siguientes links:
[1] W3 Organization’s Global Structure of an HTML Document and Meta Tags
[2] header.php code y tutoriales: robots, googlebot, and msnbot. metadatos en páginas, post simples, página principal…
[3] UC Berkeley’s Guide to Search Engines
[4] SearchengineWatch.com: how to use metadata
[5]webdeveloper’s metatag information

Parse XML file into array (PHP)

Nowadays XML-based information storing is increasing its use, so usually you’ll have to parse these files. If you want a PHP function that resolves these needs, take a look at this:

&lt;?php
/**
 * xml2array() will convert the given XML text to an array in the XML structure.
 * Link: http://www.bin-co.com/php/scripts/xml2array/
 * Arguments : $contents - The XML text
 *                $get_attributes - 1 or 0. If this is 1 the function will get the attributes as well as the tag values - this results in a different array structure in the return value.
 *                $priority - Can be 'tag' or 'attribute'. This will change the way the resulting array sturcture. For 'tag', the tags are given more importance.
 * Return: The parsed XML in an array form. Use print_r() to see the resulting array structure.
 * Examples: $array =  xml2array(file_get_contents('feed.xml'));
 *              $array =  xml2array(file_get_contents('feed.xml', 1, 'attribute'));
 */
function xml2array($contents, $get_attributes=1, $priority = 'tag') {
    if(!$contents) return array();
 
    if(!function_exists('xml_parser_create')) {
        //print "'xml_parser_create()' function not found!";
        return array();
    }
 
    //Get the XML parser of PHP - PHP must have this module for the parser to work
    $parser = xml_parser_create('');
    xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8"); # http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
    xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
    xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
    xml_parse_into_struct($parser, trim($contents), $xml_values);
    xml_parser_free($parser);
 
    if(!$xml_values) return;//Hmm...
 
    //Initializations
    $xml_array = array();
    $parents = array();
    $opened_tags = array();
    $arr = array();
 
    $current = &$xml_array; //Refference
 
    //Go through the tags.
    $repeated_tag_index = array();//Multiple tags with same name will be turned into an array
    foreach($xml_values as $data) {
        unset($attributes,$value);//Remove existing values, or there will be trouble
 
        //This command will extract these variables into the foreach scope
        // tag(string), type(string), level(int), attributes(array).
        extract($data);//We could use the array by itself, but this cooler.
 
        $result = array();
        $attributes_data = array();
 
        if(isset($value)) {
            if($priority == 'tag') $result = $value;
            else $result['value'] = $value; //Put the value in a assoc array if we are in the 'Attribute' mode
        }
 
        //Set the attributes too.
        if(isset($attributes) and $get_attributes) {
            foreach($attributes as $attr => $val) {
                if($priority == 'tag') $attributes_data[$attr] = $val;
                else $result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
            }
        }
 
        //See tag status and do the needed.
        if($type == "open") {//The starting of the tag '&lt;tag>'
            $parent[$level-1] = &$current;
            if(!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag
                $current[$tag] = $result;
                if($attributes_data) $current[$tag. '_attr'] = $attributes_data;
                $repeated_tag_index[$tag.'_'.$level] = 1;
 
                $current = &$current[$tag];
 
            } else { //There was another element with the same tag name
 
                if(isset($current[$tag][0])) {//If there is a 0th element it is already an array
                    $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
                    $repeated_tag_index[$tag.'_'.$level]++;
                } else {//This section will make the value an array if multiple tags with the same name appear together
                    $current[$tag] = array($current[$tag],$result);//This will combine the existing item and the new item together to make an array
                    $repeated_tag_index[$tag.'_'.$level] = 2;
 
                    if(isset($current[$tag.'_attr'])) { //The attribute of the last(0th) tag must be moved as well
                        $current[$tag]['0_attr'] = $current[$tag.'_attr'];
                        unset($current[$tag.'_attr']);
                    }
 
                }
                $last_item_index = $repeated_tag_index[$tag.'_'.$level]-1;
                $current = &$current[$tag][$last_item_index];
            }
 
        } elseif($type == "complete") { //Tags that ends in 1 line '&lt;tag />'
            //See if the key is already taken.
            if(!isset($current[$tag])) { //New Key
                $current[$tag] = $result;
                $repeated_tag_index[$tag.'_'.$level] = 1;
                if($priority == 'tag' and $attributes_data) $current[$tag. '_attr'] = $attributes_data;
 
            } else { //If taken, put all things inside a list(array)
                if(isset($current[$tag][0]) and is_array($current[$tag])) {//If it is already an array...
 
                    // ...push the new element into that array.
                    $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
 
                    if($priority == 'tag' and $get_attributes and $attributes_data) {
                        $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
                    }
                    $repeated_tag_index[$tag.'_'.$level]++;
 
                } else { //If it is not an array...
                    $current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
                    $repeated_tag_index[$tag.'_'.$level] = 1;
                    if($priority == 'tag' and $get_attributes) {
                        if(isset($current[$tag.'_attr'])) { //The attribute of the last(0th) tag must be moved as well
 
                            $current[$tag]['0_attr'] = $current[$tag.'_attr'];
                            unset($current[$tag.'_attr']);
                        }
 
                        if($attributes_data) {
                            $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
                        }
                    }
                    $repeated_tag_index[$tag.'_'.$level]++; //0 and 1 index is already taken
                }
            }
 
        } elseif($type == 'close') { //End of tag '&lt;/tag>'
            $current = &$parent[$level-1];
        }
    }
 
    return($xml_array);
}
</code>

An example of how to use it:

Suppose you have an XML file like:

<object><name>Objeto3</name><id>003</id><referenceNumber></referenceNumber><groupIdentifier></groupIdentifier><persistentIdentifier></persistentIdentifier><masterCreationDate locale="CEST"><date format="yyyyMMdd">20090612</date><time format="HHmmssSSS">103530703</time></masterCreationDate><objectComposition>simple</objectComposition><structuralType><name></name><extension></extension></structuralType><hardwareEnvironment>x86</hardwareEnvironment><softwareEnvironment>OS: Windows XP 5.1, JVM:Sun Microsystems Inc. 1.6.0_13</softwareEnvironment><installationRequirements></installationRequirements><accessInhibitors></accessInhibitors><accessFacilitators></accessFacilitators><quirks></quirks><metadataRecordCreator></metadataRecordCreator><metadataCreationDate locale="CEST"><date format="yyyyMMdd">20090612</date><time format="HHmmssSSS">103530734</time></metadataCreationDate><comments></comments><files><file xmlns:nz_govt_natlib_xsl_XSLTFunctions="nz.govt.natlib.xsl.XSLTFunctions">
<fileIdentifier/>
<path>D:\00077-70\00077-70_0001.jpg</path>
<filename>
<name>00077-70_0001.jpg</name>
<extension>jpg</extension>
</filename>
<size>162096</size>
<fileDateTime>
<date format="yyyyMMdd">20090303</date>
<time format="HHmmssSSS">133008000</time>
</fileDateTime>
<mimetype>image/jpeg</mimetype>
<fileFormat>
<format>JPEG</format>
</fileFormat>
<image>
<imageResolution>
<samplingFrequencyUnit>2</samplingFrequencyUnit>
<xsamplingFrequency>150</xsamplingFrequency>
<ysamplingFrequency>150</ysamplingFrequency>
</imageResolution>
<imageDimension>
<width>998</width>
<length>1321</length>
</imageDimension>
<bitsPerSample>8</bitsPerSample>
<photometricInterpretation>YCbCr</photometricInterpretation>
<iccprofileName/>
<colorMap/>
<orientation>0degrees</orientation>
<compression>
<scheme>6</scheme>
<level/>
</compression>
</image>
</file>
</files></object>

Then you'll parse it like:

<code>
<strong>$xml = xml2array($contents);</strong>
$query1 = "INSERT into `master` values (" .
 		$ultimoID . ", " .
 		<strong>$xml[Object][Files][File][Size] . ", '" .
 		$xml[Object][Files][File][Filename][Extension] . "', '" .
 		$xml[Object][Files][File][Mimetype] . "', '" .</strong>
 		md5_file($listado[$i]) . "', " .
 		$xml[Object][Files][File][FileDateTime][Date] . ", " .
 		substr($xml[Object][Files][File][FileDateTime][Time],0,6) . ", '" .
 		$xml[Object][Files][File][Filename][Name] . "', '" .
 		addslashes($xml[Object][Files][File][Path]) . "', " .
 		$xml[Object][Files][File][Image][ImageResolution][XSamplingFrequency] . ", " .
 		$xml[Object][Files][File][Image][ImageResolution][YSamplingFrequency] . ", " .
 		$xml[Object][Files][File][Image][ImageDimension][Width] . ", " .
 		$xml[Object][Files][File][Image][ImageDimension][Length] . ", '" .
 		$xml[Object][Files][File][FileFormat][Format] . "', '" .
 		$xml[Object][Files][File][FileFormat][Version] . "', " .
 		$xml[Object][Files][File][Image][BitsPerSample]. ")";
</code>