zend-server-ce

02c. Instalar Zend Server CE 6.1.0 en Ubuntu (13.04/12.10/12.04/11.10)

En el momento de la redacción de este artículo (05/08/2013), la versión actual descargable de Zend Server es la 6.1.0 (válida para PHP 5.3 y PHP5.4).
Si queremos versiones anteriores (por ejemplo Zend Server 5.6 para PHP 5.2.x y 5.3.x) podemos encontrar el correspondiente instalador aqui. También podéis visitar el tutorial que escribí el año pasado para más información sobre la instalación de la versión 5.6.0.

Para instalarlo seguiremos los siguientes pasos:

1. Descargamos el paquete de instalación (Zend Server (DEB/RPM Installer Script) con nombre ZendServer-X.X.X-RepositioryInstaller-linux.tar.gz) desde el sitio oficial de Zend (http://www.zend.com/en/products/server/downloads) y lo descomprimimos.

2. Accedemos al directorio creado tras descomprimirlo.

# cd ZendServer-RepositoryInstaller-linux/

3. Dependiendo de la versión de PHP que queramos instalar (5.3 o 5.4) ejecutaremos:

  • Para PHP 5.3: # sudo ./install_zs.sh 5.3
  • Para PHP 5.4: # sudo ./install_zs.sh 5.4

Observa que al requerirse privilegios de root para poder realizar la instalación se te solicitará la contraseña tras ejecutar alguna de las anteriores sentencias.

Entre los mensajes que aparecerán por consola mientras se está realizando la instalación, resultan útil conocer los siguientes:

To activate the new configuration, you need to run:  service apache2 restart
Enabling module rewrite
To activate the new configuration, you need to run:  service apache2 restart
Enabling site zendserver_gui.conf
To activate the new configuration, you need to run:  service apache2 reload

y

Apache configuration file was backedup successfully:
/etc/apache2/sites-enabled/000-default => /usr/local/zend/var/backups/000-default
Adding Include “/usr/local/zend/etc/sites.d/zend-default-vhost-80.conf” to file: /etc/apache2/sites-enabled/000-default, 1

Al acabar la instalación aparecerá una notificación de que ha finalizado, indicando también que se ha arrancado el servidor.

Una vez finalice la instalación podemos ingresar vía web al panel de administración de Zend Server, desde las siguientes URLs (casi con toda probabilidad el <hostname> será 127.0.0.1 ó localhost):

– Modo seguro: https://<hostname>:10082/ZendServer/

– Modo normal: http://<hostname>:10081/ZendServer/

 

Error: Error 330 (net::ERR_CONTENT_DECODING_FAILED): Error desconocido.

El problema puede venir ocasionado por varias causas:

1. No está instalada la extensión zlib.

Nota: ob_gzhandler() requiere la extensión zlib.

2. No está habilitado el soporte de compresión de output buffer en el php.ini.

Para solucionarlo comprobaremos/estableceremos que zlib.output_compression está activo en el php.ini:

output_handler =
zlib.output_compression = On

También podríamos solucionarlo añadiendo al fichero .htaccess la siguiente línea:

php_flag zlib.output_compression on

IMPORTANTE: Si dejamos habilitado zlib.output_compression, deberemos asegurarnos que en el código no hay ninguna llamada a ob_gzhandler(), ya que ambos sistemas no pueden convivir. Ver punto siguiente

3. Se están usando juntos ob_gzhandler() y zlib.output_compression

Nota: No se pueden usar juntos ob_gzhandler() y zlib.output_compression. Observe también que se prefiere el uso de zlib.output_compression antes que ob_gzhandler().

Usar simultáneamente ambos sistemas de compresión provocaría que se comprimiese dos veces el output buffer, una vez por parte de zlib.output_compression y la segunda por parte de ob_gzhandler(), con lo que los contenidos del buffer doblemente comprimidos se enviarían al navegador, el cual no entendería la codificación de la información (ya que descomprimiría lo recibido una sola vez esperando ya encontrarse con el HTML a mostrar pero lo que se encontraría es con algo nuevamente comprimido, lo cual no entendería, provocando posiblemente el siguiente error (lo siguiente es lo que muestra Chrome por ejemplo):

Esta página web no está disponible

Es posible que la página web http://xxxxxxxx.xxx/ esté temporalmente inactiva o que se haya trasladado definitivamente a otra dirección.
Error 330 (net::ERR_CONTENT_DECODING_FAILED): Error desconocido.

If you’re using Apache’s gzip compression already (which by the looks of it you do, since CSS/JS are compressed as far as I can see), then using ob_start(‘ob_gzhandler’) will compress that compression… and the browser won’t be able to handle it.

I’m speaking under correction of course as I’ve always used Apache to do it for me, but it looks like you’re double gzipping things, that’s why it works without the “ob_gzhandler” statement.

Check your .htaccess files on the other sites and compare it with the site you’re running that has the same problem as I can’t see that you’re running Drupal on zacoders.net and jadeit.co.za, just on jadeit.co.za which makes me think they’re not the same framework as you said they are? shrug

answered Mar 26 ’11 at 11:14
AcidRaZor
41839
Just to add on to my answer here, the reason why curl returns the website normally is because (again, AFAIK) it doesn’t support gzip thus the server/software will give the site “as is”. But with browsers that do support gzip and tell the server they support it, will get back the gzipped-zipped content and can’t deflate it properly. – AcidRaZor Mar 26 ’11 at 11:16
You are right in saying that it will compress the compressed data, but I prevented it from happening by doing if (ob_get_level() === 0) { ob_start(‘ob_gzhandler’); } – Jrgns Mar 26 ’11 at 11:28
zacoders runs on backend-php, as does the actualy backend site and jrgns.net. I also use it at the company I work for. I haven’t had a chance to port jadeit.co.za to it…🙂 – Jrgns Mar 26 ’11 at 11:30
Well, try disabling Apache’s gzip then (using .htaccess if Texo allowed that) and see if it works. And AFAIK, ob_get_level only checks if ob_start has been called before. It doesn’t stop the page from being gzipped the first time since ob_start() was called before the ob_get_level check so that if statement will always be true and thus always compress the compressed page. – AcidRaZor Mar 26 ’11 at 11:46
ob_get_level returns the current output buffering level. If it’s > 0 without you calling ob_start, you know that PHP is doing the gzipping. I managed to sort out the issue by just closing of all output buffering before ending the script. But this is the funny part. When a script ends, PHP automatically flushes all output buffers one by one. For all the other sites this didn’t cause any problems. For this one it did. Doing it explicitly solved the problem, but I’m still not sure what the exact problem is!🙂 – Jrgns Mar 28 ’11 at 10:25

ust Put this line of code in starting and everything will be fine..

while (ob_get_level() > 0) { ob_end_clean() ; } 

Ejecutar scripts PHP mediante CRON y cURL

CRON es un componente crucial de un servidor web. Es el encargado de ejecutar tas tareas planificadas, como notificaciones, mailings, backups y cosas por el estilo.

Crear una nueva tarea o trabajo (cron job) es muy sencillo, simplemente con el comando:

crontab -e

Este comando te llevará al editor de trabajos de cron donde se introducen los instantes en los que se deben ejecutar ciertas sentencias o scripts/ficheros ejecutables.

Para ahorrarnos tiempo:

@hourly /home/usuario/script1.sh
@daily /home/usuario/script2.sh

ejecutará los scripts especificados cada hora y cada día respectivamente. En las páginas del manual de cron podemos encontrar ejemplos más avanzados para configurar los trabajos.

De acuerdo a la documentación de documentación de PHP, ejecutar un script PHP mendiante CLI es tan simple como hacer lo siguiente:

php -f mi_script.php

Sin embargo, hay varias cosas que no están disponibles en la versión CLI (interfaz de línea de comandos) de PHP, por ejemplo cuando queremos llamar a una URL semántica (que el sistema web luego mapeará a una ruta de un script PHP) pero no conocemos explicitamente cual es la ruta y/o parámetros concretos de ese script .

Para solucionar este problema nos valdremos de cURL (también podríamos conseguirlo con wget, o usar lynx, … hay varias formas de conseguir el mismo resultado) ya que cURL puede realizar llamadas a URLs como si fuesen visitas de un usuario desde su navegador. Basándonos en esto, podemos ajustar cURL para que ejecute un script que esté disponible vía URL.
Por ejemplo tendríamos el script1.sh referenciado anteriormente por cron podría ser:

#!/bin/bash
curl http://test.com/test_asdasdasdasdasd.php

Lo que haría que cada hora cron llamase a ese script sh, el cual a su vez haría que se ejecutase el script php referenciado el esa URL.

Error: Warning: ob_start(): output handler ‘ob_gzhandler’ conflicts with ‘zlib output compression’

In my PHP 5.4.x setup, an old project which uses ob_start() and ob_whatever() started to throw errors like Warning: ob_start(): output handler 'ob_gzhandler' conflicts with 'zlib output compression'. On my development environment, I suppress this error by disabling zlib compression on this specific vhost.

The error

Below is the full error message I get.

1
2
3
Warning: ob_start(): output handler 'ob_gzhandler' conflicts with 'zlib output compression' in /home/lysender/projects/foo/bar/index.php on line 6
 
Notice: ob_start(): failed to create buffer in /home/lysender/projects/foo/bar/index.php on line 6

After a quick research, I got a hint that disabling the zlib compression will suppress this error. Since I’m on a vhost, I can just disable this zlib compression only in this specific project. Therefore firing up vim, I added this line on my vhost definition.

1
2
3
...
    php_value zlib.output_compression off
</VirtualHost>

Then restarted apache, then the warning is gone.

1
/etc/rc.d/rc.httpd restart

I haven’t experienced this much problem when I’m using PHP 5.3.x and even the early PHP 5.4.x on my Linux distro. Maybe something has changed that made this warning cry wolf.

zend-server-ce

02b. Instalar Zend Server CE 6.0.0 en Ubuntu 12.10

Si buscas un servidor PHP rápido, fiable y fácil para Ubuntu, Zend Server es probablemente lo que necesitas. Y si estás desarrollando tus aplicaciones PHP en Zend Framework mejor todavía.

En numerosas ocasiones he necesitado realizar diversos tipos de instalación, incluyendo hacer instalaciones completas LAMP desde cero componente a componente. En estos casos el mantenimiento y configuración suele ser problemático (sobre todo por la habitual falta personal de documentación acerca de la configuración) y hace que la mayoría de mis instalaciones sean un poco incontrolable después de un año o más.

Buscando una solución a este problema me encontré con Zend Server (en concreto la versión Community Edition, que es un stack PHP totalmente gratuito respaldado y construido por la gente de Zend Technologies Ltd).

Si alguna vez has buscado frameworks PHP te sonará al menos Zend Framework, el cual enlaza muy bien con el servidor de Zend. Así que si estás desarrollando aplicaciones PHP usando Zend Framework, el Zend Server te hará la vida mucho más fácil.

Zend Server es un conjunto de aplicaciones para PHP pre-integradas compuesto entre otras de:

– La versión más actual de la instalación y configuración de tu servidor (VPS, Amazon AMI etc)
– Acelerador de Bytecode (Optimizer+),
– Zend Data Cache
– Distribución certificada de PHP
– Zend Framework
– Servidor web Apache (o integración IIS)
– Conectividad a todas las bases de datos comunes
– Conectividad con código Java
– Panel de Administración basado en web

Hace poco he tenido que configurar rápidamente un servidor Ubuntu con Zend Server CE, por lo que os comentaré los distintos pasos y comandos necesarios para poder realizar dicha instalación y configuración en tiempo record.

NOTA: Recomiendo encarecidamente que la instalación de Zend Server CE se realice sobre un servidor Ubuntu nuevo o limpio ya que Zend Server instala y preconfigura sus propios servidor(es) web, distribución de PHP, bases de datos, etc… Si ya tienes Apache, PHP, etc… instalado en tu máquina es posible que tengas algún problema de conflictos entre la instalación a realizar y el software y configuraciones ya existentes.

Además, es requisito imprescindible tener acceso sudo a la máquina donde lo vayamos a instalar. Pongámonos manos a la obra!!!

Instalación de Zend Server CE (Community Edition) en Ubuntu 12.10

1. Utilizando el instalador

En el momento de la redacción de este artículo (23/02/2013), la versión actual descargable de Zend Server es la 6.0.0 (válida para PHP 5.3 y 5.4).

Para instalarso seguiremos los siguientes pasos:

1. Descargamos el script de instalación (disponible en RPM/DEB) desde el sitio oficial de Zend.

2. Accedemos al directorio creado tras descomprimirlo. Dependiendo de si queremos instalar Zend Server con soporte para PHP 5.3 o 5.4 ejecutaremos:

– Para PHP 5.3: # sudo ./install_zs.sh 5.3
– Para PHP 5.4: # sudo ./install_zs.sh 5.4

2. Desde los repositorios oficiales

Comienza agregando los repositorios oficiales Zend para tu distribución:

Edita el archivo sources.list:

# sudo nano /etc/apt/sources.list

Añade la siguiente sentencia en  la parte inferior del archivo:

deb http://repos.zend.com/zend-server/deb servidor non-free

Agrega la llave pública del repositorio de Zend:

# http://repos.zend.com/zend.key wget-O-| sudo apt-key add –

Sincroniza los repositorios:

# sudo apt-get update

Finalmente instala Zend Server con PHP 5.3 (o 5.4, a tu elección)

# sudo apt-get install zend-server-ce-php-5.3

Una vez finalizada la instalación

Si has seguido los pasos indicados en el punto 1 o en el punto 2 anteriores deberías tener ya Zend Server CE correctamente instalado!

Podremos acceder vía web al panel de administración de Zend Server, desde las siguientes URLs:

https://localhost:10082/ZendServer/  (Modo seguro https)
http://localhost:10081/ZendServer/    (Modo normal http)

Si no pudieses acceder  utilizando localhost, prueba a cambiar localhost por la IP de la máquina (por ejemplo 127.0.0.1, o la IP pública o nombre de dominio de la máquina).

REFERENCIAS

http://files.zend.com/help/Zend-Server-6/zend-server.htm

HTML5-Drag-and-Drop-File-Upload

[HTML5] Upload de Ficheros con HTML5, Drag and Drop y Canvas

HTML5 drag and drop (dnd) file upload with canvas using Drag and Drop API, HTML5 File API, jQuery Template and CSS3 example with tutorial.

Over the last week I’ve been trying to learn about HTML5 Drag and Drop (DnD) File Upload using Drag and Drop API combined with HTML5 File API. Drag and Drop API enables browsers to receive local files from user desktop just by ‘drag‘ and then ‘drop‘. While HTML5 File API enables JavaScript to interact with selected local files before upload to the server. With both of them, we can build a drag and drop file upload interface.

HTML5-Drag-and-Drop-File-Upload

View Demo Download

I’ve created an example of HTML5 drag and drop file upload with the capabilities to resize, crop or reformat/filter the uploaded image through HTML5 canvas before sending to the server. The techniques included are:

You could check out the working demo through the link up there. Have fun with this HTML5 drag and drop file upload with canvas!

This working demo only works in browsers that support HTML5 File API as well as Drag and Drop API.

The HTML Markup


The demo consists of two major parts, an area for drag and drop as well as the upload preview part. The idea is to capture the files uploaded by user whether through drag and drop area or browse button, and then display it in the preview section before upload to the server.

<!-- drop area -->
<div id="droparea">
	<div class="dropareainner">
		<p class="dropfiletext">Drop files here</p>
		<p>or</p>
		<p><input id="uploadbtn" type="button" value="Select Files"/></p>
		<!-- extra feature -->
		<p id="err"><!-- error message --></p>
	</div>
	<input id="upload" type="file" multiple />
</div>
<!-- result area -->
<div id="result"></div>

Also, we will using jQuery Template to create image preview wrapper which contains preview of uploaded image, image name, original image size, and final size of image that will upload to the server.

<script id="imageTemplate" type="text/x-jquery-tmpl"> 
    <div class="imageholder">
		<figure>
			<img src="${filePath}" alt="${fileName}"/>
			<figcaption>
				${fileName} <br/>
				<span>Original Size: ${fileOriSize} KB</span> <br/>
				<span>Upload Size: ${fileUploadSize} KB</span>
			</figcaption>
		</figure>
	</div>
</script>

Events Binding


Now, let’s get our hand dirty with some jQuery coding. The first thing that we are going to do is to bind the Drag and Drop events to our #droparea element. There are a number of drag events, but we will use only dragover, dragleave, and drop events.

var dropzone = $('#droparea');

dropzone.on('dragover', function() {
	//add hover class when drag over
	dropzone.addClass('hover');
	return false;
});

dropzone.on('dragleave', function() {
	//remove hover class when drag out
	dropzone.removeClass('hover');
	return false;
});

dropzone.on('drop', function(e) {
	//prevent browser from open the file when drop off
	e.stopPropagation();
	e.preventDefault();
	dropzone.removeClass('hover');

	//retrieve uploaded files data
	var files = e.originalEvent.dataTransfer.files;
	processFiles(files);

	return false;
});

dataTransfer.files will returns a FileList of the files being dragged.

Not only drag and drop area, instead we will create a ‘browse’ button as well. But we don’t want to use native browser file upload button, so here is a little trick for this.

var uploadBtn = $('#uploadbtn');
var defaultUploadBtn = $('#upload');

uploadBtn.on('click', function(e) {
	e.stopPropagation();
	e.preventDefault();
	//trigger default file upload button
	defaultUploadBtn.click();
});

defaultUploadBtn.on('change', function() {
	//retrieve selected uploaded files data
	var files = $(this)[0].files;
	processFiles(files);	
	return false;
});

The best practice of using new HTML5 objects is to check for object availability before using it, else simply skips the function or show a fallback plan if any.

function processFiles(files) {
	//check for browser support	
	if(files && typeof FileReader !== "undefined") {
		//extract FileList as File object
		for(var i=0; i<files.length; i++) {
			readFile(files[i]);
		}
	}
	else {
		//some message or fallback
	}
}

Each File object contains properties like name, size, and type.

The FileReader Object


Next, we will read the content of uploaded file through FileReader object which allows browser asynchronously to read files from user desktop.

var readFile = function(file) {
	if( (/image/i).test(file.type) ) {
		//define FileReader object
		var reader = new FileReader();

		//init reader onload event handlers
		reader.onload = function(e) {	
			var image = $('<img/>')
			.load(function() {
				//get new image URL from canvas image
				var newimageurl = getCanvasImage(this);

				//create preview using jQuery Template
				createPreview(file, newimageurl);

				//upload the new image to server
				uploadToServer(newimage, imageObj);

			})
			.attr('src', e.target.result);	
		};

		//begin reader read operation
		reader.readAsDataURL(file);
	} else {
		//some message for wrong file format
	}
}

Once the reader is loaded, we can bind the uploaded image (in Base64-encoded) to <img> tag through e.target.result. After that, we will use it to draw a canvas image so that we can modify it with JavaScript before proceed to the server. This definitely will save the bandwidth compare to process it at server side.

Canvas Image


We can resize the image as well as apply different filters to our canvas image. However I won’t cover them here, still, you can grab them from the source files. When the canvas image was drawn, convert it to file URL format through toDataURL method and attach it to jQuery Template.

var getCanvasImage = function(image) {

	var imgWidth = 180,
	    imgHeight = 180;

	//define canvas image
	var canvas = document.createElement('canvas');
	canvas.width = imgWidth;
	canvas.height = imgHeight;
	var ctx = canvas.getContext('2d');
	ctx.drawImage(image, imgWidth, imgHeight);

	//convert canvas to jpeg URL
	return canvas.toDataURL("image/jpeg");
}

jQuery Template


When we have everything we need, simply bind them to jQuery Template and append to our #result element.

var createPreview = function(file, newURL) {	
	//populate jQuery Template binding object
	var imageObj = {};
	imageObj.filePath = newURL;
	imageObj.fileName = file.name.substr(0, file.name.lastIndexOf('.')); //subtract file extension
	imageObj.fileOriSize = convertToKBytes(file.size);
	imageObj.fileUploadSize = convertToKBytes(dataURItoBlob(newURL).size); //convert new image URL to blob to get file.size

	//append new image through jQuery Template
	var img = $("#imageTemplate").tmpl(imageObj).prependTo("#result");
}

There is a need of converting newURL (in DataURI format) to Blob so that we can access it’s attributes like file size or file type whether from client side or server side.

Upload To Server


Last but not least, we can upload the new image to our server through jQuery AJAX.

var uploadToServer = function(oldFile, newFile) {
	// prepare FormData
	var formData = new FormData();  
	//we still have to use back some of old file data 
	//since new file doesn't contains original file data
	formData.append("filename", oldFile.name);
	formData.append("filetype", oldFile.type);
	formData.append("file", newFile); 

	//submit formData using $.ajax			
	$.ajax({
		url: "upload.php",
		type: "POST",
		data: formData,
		processData: false,
		contentType: false,
		success: function(data) {
			//some success code here
		}
	});	
}

Script PHP de Upload de Ficheros


Though this is not a complete PHP upload script, but hope this will helps you get to know how it works inside the back end code.

if(isset($_FILES['file'])) {
	if(move_uploaded_file($_FILES["file"]["tmp_name"], 
						"upload/".$_POST["filename"])) {
		echo 'Upload Success';
	} else {
		echo '#Fail';
	}
}

[ZF] Paginación con Zend_Paginator

En el controller (/application/default/controllers/MediabrowserController.php):

public function imagesListPrivate(){ ini_set('max_execution_time', 300); //300 seconds = 5 minutes // Modelo $cc_media_image = Sqy_Db_Table::getInstance('cc_media_image'); // Se obtienen todas las imágenes que coincidan con el filter y category recibidos por POST //$images = $cc_media_image->getAll(@trim($_POST['filter']), @trim($_POST['category'])); //$images = $cc_media_image->getAll(@trim($_POST['filter']), @trim('cocineros')); $images = $cc_media_image->getAll(@trim($_POST['filter']), @trim('recursos'));
// PAGINACION CON Zend_Paginator:
//    Method                     Description
//    setCurrentPageNumber     Sets the current page number (default 1).
//    setItemCountPerPage     Sets the maximum number of items to display on a page (default 10).
//    setPageRange             Sets the number of items to display in the pagination control (default 10). Note: Most of the time this number will be adhered to exactly, but scrolling styles do have the option of only using it as a guideline or starting value (e.g., Elastic).
//    setView                 Sets the view instance, for rendering convenience.
//$paginator = new Zend_Paginator(new Zend_Paginator_Adapter_Array($images));
$paginator = Zend_Paginator::factory($images);
$paginator->setCurrentPageNumber($this->_getParam('page'));
$paginator->setItemCountPerPage(50);
$paginator->setPageRange(10);
$paginator->setView($this->view);
//Zend_Paginator::setDefaultScrollingStyle('Sliding');
//Zend_View_Helper_PaginationControl::setDefaultViewPartial('mediabrowser/my-pagination-control.phtml');
$this->view->paginator     = $paginator;
$this->view->images         = $images;
$this->view->JsonData     = $images;
}

En la vista (/application/default/views/private/mediabrowser/images-list.phtml):

<ul>
<?php
echo $this->paginationControl($this->paginator,'All','mediabrowser/my-pagination-control-1.phtml');
//echo $this->paginationControl($this->paginator,'Elastic','mediabrowser/my-pagination-control-2.phtml');
//echo $this->paginationControl($this->paginator,'Jumping','mediabrowser/my-pagination-control-3.phtml');
//echo $this->paginationControl($this->paginator,'Sliding','mediabrowser/my-pagination-control-4.phtml');
?>
<?php if($this->paginator){ ?>
<?php foreach($this->paginator as $key=> $image){?>
<li>
<?php
//echo Zend_Json::encode($image);
echo 'id: <strong>'.$image['id'].'</strong> ('.$image['width'].'x'.$image['height'].')';
echo '<a onclick="return hs.expand(this, { headingText: \'Original ('.$image['width'].'x'.$image['height'].') \' })" href="'.$image['path'].$image['name'].'">';
echo "<img title='{$image['name']}' src='/block/mediabrowser/image-zoom?f={$image['path']}{$image['name']}&w=120&h=90' />";
echo '</a>';
//echo $image['name'];
?>
</li>
<?php ob_flush(); ?>
<?php } ?>
<?php } ?>
</ul>

Vista para la navegacion de la paginación (/application/default/views/private/mediabrowser/my-pagination-control-1.phtml):

<!-- Ver: http://www.duckheads.co.uk/zend-paginator-example/265 --> <style> .paginationControl{padding:10px;font-family:arial;font-size:12px;color:#333;height:13px} .paginationControl_showing{float:left;position:relative;font-weight:bold} .paginationControl_showing strong.current{color:#a8050d} .paginationControl_pages{float:right;position:relative;font-weight:bold;color:#494848} .paginationControl_pages a{color:#494848} .paginationControl_pages strong{color:#a8050d} .paginationControl_pages .previous,.paginationControl_pages .next{color:#a8050d} </style> <?php if ($this->pageCount) : $midRange = floor(sizeof($this->pagesInRange) / 2); if (($this->current + $midRange) < $this->pageCount) : array_pop($this->pagesInRange); $display_end = true; endif; ?> <div> <div> Página <strong><?= $this->current; ?></strong> de <strong><?= $this->last; ?></strong> </div> <div> <!-- Previous page link --> <?php if (isset($this->previous)): ?> <a href="<?php echo $this->url(array('page' => $this->previous)) . $this->query; ?>">&lt; Anterior</a> | <?php else: ?> <span>&lt; Anterior</span> | <?php endif; ?> <?php if (($this->current - $midRange) > $this->first): ?> <?php array_shift($this->pagesInRange);?> <a href="<?php echo $this->url(array('page' => $this->first)) . $this->query; ?>" ><?php echo $this->first ?></a>... | <?php endif; ?> <!-- Numbered page links --> <?php foreach ($this->pagesInRange as $page): ?><?php if ($page != $this->current): ?> <a href="<?php echo $this->url(array('page' => $page)) . $this->query; ?>" ><?= $page; ?></a> | <?php else: ?> <strong><?= $page; ?></strong> | <?php endif; ?> <?php endforeach; ?> <?php if (!empty($display_end)) : ?> ...<a href="<?php echo $this->url(array('page' => $this->last)) . $this->query; ?>" ><?php echo $this->last ?></a> | <?php endif; ?> <!-- Next page link --> <?php if (isset($this->next)): ?> <a href="<?php echo $this->url(array('page' => $this->next)) . $this->query; ?>">Siguiente &gt;</a> <?php else: ?> <span>Siguiente &gt;</span> <?php endif; ?> </div> </div> <?php endif; ?>