NextGen Gallery tweaks

I have recently redesigned the website of my most historic client, the Scottish Volleyball Association, and the most challenging part has been to migrate the 550 galleries from the old symfony 1.x structure to the WordPress.

After trying quite a few solutions, I have settled for the most popular WordPress plugin, NextGen Gallery (Pro version) and never looked back.

The migration process has been quite straightforward (more on this in another post maybe later) and some technical issues have been quickly answered by the very efficient support team.

Some customization was however required due to the complex set up

For those using NextGen already, I have created my albums as main container per year (eg. 2019, 2018) and then attached my galleries into each album. This allowed me to have to build only 12 pages to display the galleries from 2007 to 2019 with the following simple shortcode in each page.

[ngg src=”albums” ids=”xx” display=”basic_extended_album” order_direction=”ASC” template=”basic-album”]

The first problem I have faced  was, because I didn’t have a post attached to each gallery there was no easy way for me to display the title the way I wanted in each gallery page.

Breadcumbs template to the rescue

I have however noticed that by enabling the breadcrumbs in the basic extended album setting the gallery title was retrieved by the plugin. I have thus decided to override this template and quickly came up with the following code to display the gallery title in the page.

<?php
$end = end($breadcrumbs);
?>
<?php if(array_key_exists('name', $end)): ?>
<h2><?php echo $end['name']; ?></h2>
<?php endif; ?>

The second problem I noticed was the breadcrumb (included with Yoast SEO) which was not retrieving the gallery title either since there was no dedicated page created.

This time I have used a more complex filter to overcome my issue which is looking for a nggallery string in the request URI (default slug used by NextGen) and retrieve the matching album to append the correct breadcrumb to the existing array.

add_filter('wpseo_breadcrumb_links', 'yoast_seo_breadcrumb_append_link');
function yoast_seo_breadcrumb_append_link($links)
{
    $request_parts = explode('/', $_SERVER['REQUEST_URI']);
    if (in_array('nggallery', $request_parts)) {
        $gallery_slug = end($request_parts);
        global $wpdb;
        $gallery = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}ngg_gallery WHERE `slug` = '$gallery_slug'", ARRAY_A);
        if ($gallery) {
            $links[] = array(
                'text' => $gallery[0]['title'],
            );
        }
    }

    return $links;
}

I hope this would help someone with the same set up one day.

Do not hesitate to comment if you have any question.

Customize Prestashop CMS pages

For some unexplicable reason Prestashop has decided to have a single "Meta title" field in CMS page edition. However you most often need to have a meta title different from your page title (h1), which could also be different from the label used in a menu.

In order to add those additional fields in our CMS admin we are going to create a module and register a hook on the AdminCmsFormModifier. Find below the full module code

Once your module is installed and database updated you should be able to edit the 2 new created fields.

Now to use the "real_meta_title" field override the Meta class by creating a Meta.php file in "override/classes" such as

Finally, in your CMS page template you can update the "page_title" block such as

{block name='page_title'}
{$cms.page_title}
{/block}

I hope this will be useful to you. Please comment if you have come over the same issue and solved it differently.

Filter Doctrine collections

Let's assume the following scenario where you have a Product entity linked to a Category entity by a ManyToOne relationship.

In your twig template you want to loop on all categories and display a list of products for each of them. The {{ category.products }} will give you that ... no problem. Now imagine you want to retrieve only active products ordered by position. Since it is not good practice to call the Doctrine Entity Manager in your entity, here comes Doctrine Criteria Filters to the rescue.

In your Category entity create the following method

public function getActiveProducts()
    {
        $criteria = Criteria::create()
            ->where(Criteria::expr()->eq("enabled", 1))
            ->andWhere(Criteria::expr()->eq("category", $this))
            ->orderBy(array("position" => Criteria::ASC));

        return $this->getProducts()->matching($criteria);
    }

To loop on your active and ordered products you can now call the method like this in your twig template

{% for product in category.activeProducts %}

[...]

{% endfor %}

More information on the Doctrine documentation.

Sonata Media fixed dimensions resizer

We assume that you have SonataMediaBundle up and running in your project and have extended the bundle with SonataEasyExtendsBundle in your Application folder.

Next, add the following class in your extended bundle in a Resizer folder


namespace Application\Sonata\MediaBundle\Resizer;

use Imagine\Image\ImagineInterface;
use Imagine\Image\Box;
use Gaufrette\File;
use Sonata\MediaBundle\Model\MediaInterface;
use Imagine\Image\ImageInterface;
use Imagine\Exception\InvalidArgumentException;
use Sonata\MediaBundle\Metadata\MetadataBuilderInterface;
use Sonata\MediaBundle\Resizer\ResizerInterface;

class FixedDimensionsResizer implements ResizerInterface
{
    protected $adapter;

    protected $mode;

    protected $metadata;

    /**
     * @param ImagineInterface $adapter
     * @param string           $mode
     */
    public function __construct(ImagineInterface $adapter, $mode, MetadataBuilderInterface $metadata)
    {
        $this->adapter  = $adapter;
        $this->mode     = $mode;
        $this->metadata = $metadata;
    }

    /**
     * {@inheritdoc}
     */
    public function resize(MediaInterface $media, File $in, File $out, $format, array $settings)
    {
        if (!isset($settings['width'])) {
            throw new \RuntimeException(sprintf('Width parameter is missing in context "%s" for provider "%s"', $media->getContext(), $media->getProviderName()));
        }

        $image = $this->adapter->load($in->getContent());

        $content = $image
            ->thumbnail($this->getBox($media, $settings), $this->mode)
            ->get($format, array('quality' => $settings['quality']));

        $out->setContent($content, $this->metadata->get($media, $out->getName()));
    }

    /**
     * {@inheritdoc}
     */
    public function getBox(MediaInterface $media, array $settings)
    {
        $size = $media->getBox();

        if ($settings['width'] == null && $settings['height'] == null) {
            throw new \RuntimeException(sprintf('Width/Height parameter is missing in context "%s" for provider "%s". Please add at least one parameter.', $media->getContext(), $media->getProviderName()));
        }

        if($settings['constraint'] === false){
            return new Box($settings['width'], $settings['height']);
        }

        if ($settings['height'] == null) {
            $settings['height'] = (int) ($settings['width'] * $size->getHeight() / $size->getWidth());
        }

        if ($settings['width'] == null) {
            $settings['width'] = (int) ($settings['height'] * $size->getWidth() / $size->getHeight());
        }

        return $this->computeBox($media, $settings);
    }

    /**
     * @throws InvalidArgumentException
     *
     * @param MediaInterface $media
     * @param array          $settings
     *
     * @return Box
     */
    private function computeBox(MediaInterface $media, array $settings)
    {
        if ($this->mode !== ImageInterface::THUMBNAIL_INSET && $this->mode !== ImageInterface::THUMBNAIL_OUTBOUND) {
            throw new InvalidArgumentException('Invalid mode specified');
        }

        $size = $media->getBox();

        $ratios = array(
            $settings['width'] / $size->getWidth(),
            $settings['height'] / $size->getHeight()
        );

        if ($this->mode === ImageInterface::THUMBNAIL_INSET) {
            $ratio = min($ratios);
        } else {
            $ratio = max($ratios);
        }

        return $size->scale($ratio);
    }
}

 

Then, add the following service in your config.yml

 

services:
    sonata.media.resizer.fixedDimensions:
        class: Application\Sonata\MediaBundle\Resizer\FixedDimensionsResizer
        arguments: [@sonata.media.adapter.image.gd, 'outbound', @sonata.media.metadata.proxy]

Finally enabled the provider in the sonata_media config

sonata_media:
[...]
providers:
image:
resizer: sonata.media.resizer.fixedDimensions

When you add a new context for your image you can now add the following parameter ton constraint your image dimensions to the width and height specified.

sonata_media:
[...]
contexts:
my_context:
providers:
- sonata.media.provider.image
formats:
small:   { width: 400, height: 300, constraint: false}

 

Symfony 2 meets Oracle (part 3)

Configuration

First of all you will need to configure the database connection. We will assumme you have followed the previous tutorials and have an Oracle XE database.

parameters:
    database_driver: oci8 
    database_host: your_VM_IP
    database_port: '1521'
    database_name: XE
    database_user: your_username
    database_password: your_password
    database_charset: AL32UTF8

Database charset is by default AL32UTF8 in Oracle XE but if you have to move your database to another Oracle version I would recommend to specify it explicitely in the configuration.
You need to make sure you have the following parameter in your app/config/config.yml

doctrine:
    dbal:
        charset:  %database_charset%

Adding a listener

You need then to add a custom listener to handle the session properly.

services:
    oracle.listener:
        class: Doctrine\DBAL\Event\Listeners\OracleSessionInit
        tags:
            - { name: doctrine.event_listener, event: postConnect }

SonataPageBundle and Oracle

Doctrine does not support where conditions on CLOB. You need to update your schema using the following PR https://github.com/sonata-project/SonataPageBundle/pull/104

Doctrine and Oracle

Finally there are a few known issues between Oracle and Doctrine

http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/known-vendor-issues.html#oracle

Symfony 2 meets Oracle (part 2)

If you have followed the part 1 and downloaded the VM I suggested, it is important to know that your database name is called “XE”. It will be useful in next chapter on Symfony configuration.

Oracle configuration

Open a terminal and start the configuration process with the following command

> /etc/init.d/oracle-xe configure

 Create a worspace with Application Express (simple mode)

Go to Applications > Oracle Database 11G … > Get Started

Click on Application Express and login with the admin user you have previously configured (default user is sys)

Follow the process to create the environment and keep a note of your username and password.

Create a user, tablespace and permissions with the commande line (advanced mode)

Go to Applications > Oracle Database 11G … > Run SQL command line

CREATE USER DUMMYUSER IDENTIFIED BY password
DEFAULT TABLESPACE "YOURTABLESPACE" 
TEMPORARY TABLESPACE "TEMP";

ALTER USER DUMMYUSER QUOTA UNLIMITED ON FFFCNF;

GRANT create procedure, create session, create table,
      create type, create view, create synonym, create trigger, resource TO DUMMYUSER;

Environment variables configuration

Add to /etc/profile the following configuration

ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe
PATH=$PATH:$ORACLE_HOME/bin
export ORACLE_HOME
export ORACLE_SID=XE
export PATH

Then reload your profile with

> source /etc/profile

Finally restart Oracle with the following command

> /etc/init.d/oracle-xe restart

You can test the connection with the following PHP script

$conn = oci_connect('username', 'password', 'IP:1521/XE');
if (!$conn) {
    $e = oci_error();
    trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
}

If you have a listener error message read the next paragraph

Listener configuration

We are going to register the listener dynamically so it can start with our Oracle instance.

Stop the listener

> lsnrctl stop

Remove the listener.ora (just move it to keep a copy)

> mv /u01/app/oracle/product/11.2.0/xe/network/admin/listener.ora /u01/app/oracle/product/11.2.0/xe/network/admin/listener.ora.old

Restart the service

> lsnrctl start

Connect to the database

> sqlplus
Enter user-name: sys as sysdba

(Re)start an Oracle instance (shutdown first if there is one running)

SQL> shutdown
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area  204591104 bytes
Fixed Size                  2225032 bytes
Variable Size             171969656 bytes
Database Buffers           25165824 bytes
Redo Buffers                5230592 bytes
Database mounted.
Database opened.
SQL> exit
Disconnected from Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production

Check the status listener

> lsnrctl status

The following line should appear

Service "XE" has 1 instance(s).
Instance "XE", status READY, has 1 handler(s) for this service...

Troubleshooting

If you get the following error

ORA-04031: unable to allocate 3896 bytes of shared memory ("shared pool","update seq$ set increment$=:...","sga heap(1,0)","kglsim object batch") 

Connect with sqlplus and run the following queries

SQL> alter system set SHARED_POOL_RESERVED_SIZE = '64M' scope=spfile;
SQL> alter system set SHARED_POOL_SIZE = '200M' scope=spfile;

If you have messed up with your Oracle configuration and cannot start the server anymore you can access your configuration file with the following procedure

SQL> create pfile='/tmp/pfile' from spfile;
Editer le fichier /tmp/pfile en modifiant les valeurs concernées
SQL> create spfile from pfile='/tmp/pfile';
SQL> startup

Tip to know all the size parameters

SQL> show parameter size;

Références

http://jamessmith73.wordpress.com/james-smiths-java/just-like-that/oracle-xe-database-on-ubuntu/
http://edstevensdba.wordpress.com/2011/07/30/exploring-the-local_listener-parameter/
http://www.nielskrijger.com/2012/06/creating-tablespace-and-user-in-oracle.html
https://forums.oracle.com/forums/thread.jspa?threadID=2421429
http://oraclequirks.blogspot.co.uk/2008/05/ora-02095-specified-initialization.html

Symfony 2 meets Oracle (part 1)

Downloading the VM

First of all you need to get Virtualbox for your operating system

https://www.virtualbox.org/wiki/Downloads

Then you need to create an account on the Oracle website (if you don’t have one already) and get the Enterprise PHP Development VM

http://www.oracle.com/technetwork/community/developer-vm/index.html

Configuring the VM

Import the VM in Virtualbox (File > Import Appliance) and follow instructions.

Click on the appliance created  > Settings > Network and change the adapter to “Bridged Adapter”. In the advanced tab, update the “Promiscuous mode” to “Allow all”.

Start the VM and login using the root account provided. Then go in System > Administration > Network. Open the network adapter (eth0) and configure a static IP address according to your local network. Leave 0.0.0.0 for the default gateway.

Save and restart the network adapter for your configuration to be reset.

Start a terminal on your computer and execute the following commands to make sure you can reach the VM.

> ping [STATIC_IP]
> telnet [STATIC_IP] 1521

If this does not work, try to disable the firewall in System > Administration > Firewall and security

References

http://www.oracle.com/technetwork/articles/servers-storage-admin/evaluating-linux-vb-1934676.html