<?php /* * $Id: Sluggable.php 30067 2016-03-08 13:44:25Z matias $ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information, see * <http://www.doctrine-project.org>. */ /** * Easily create a slug for each record based on a specified set of fields * * @package Doctrine * @subpackage Template * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 1.0 * @version $Revision$ * @author Konsta Vesterinen <kvesteri@cc.hut.fi> */ class Doctrine_Template_Listener_Sluggable extends Doctrine_Record_Listener { /** * Array of sluggable options * * @var string */ protected $_options = array(); /** * __construct * * @param string $array * @return void */ public function __construct(array $options) { $this->_options = $options; } /** * Set the slug value automatically when a record is inserted * * @param Doctrine_Event $event * @return void */ public function preInsert(Doctrine_Event $event) { $record = $event->getInvoker(); $name = $record->getTable()->getFieldName($this->_options['name']); if ( ! $record->{$name}) { $record->{$name} = $this->buildSlugFromFields($record); } } /** * Set the slug value automatically when a record is updated if the options are configured * to allow it * * @param Doctrine_Event $event * @return void */ public function preUpdate(Doctrine_Event $event) { if (false !== $this->_options['unique']) { $record = $event->getInvoker(); $name = $record->getTable()->getFieldName($this->_options['name']); if ( ! $record->{$name} || ( false !== $this->_options['canUpdate'] && ! array_key_exists($name, $record->getModified()) )) { $record->{$name} = $this->buildSlugFromFields($record); } else if ( ! empty($record->{$name}) && false !== $this->_options['canUpdate'] && array_key_exists($name, $record->getModified() )) { $record->{$name} = $this->buildSlugFromSlugField($record); } } } /** * Generate the slug for a given Doctrine_Record based on the configured options * * @param Doctrine_Record $record * @return string $slug */ protected function buildSlugFromFields($record) { if (empty($this->_options['fields'])) { if (is_callable($this->_options['provider'])) { $value = call_user_func($this->_options['provider'], $record); } else if (method_exists($record, 'getUniqueSlug')) { $value = $record->getUniqueSlug($record); } else { $value = (string) $record; } } else { $value = ''; foreach ($this->_options['fields'] as $field) { $value .= $record->{$field} . ' '; } $value = substr($value, 0, -1); } if ($this->_options['unique'] === true) { return $this->getUniqueSlug($record, $value); } return call_user_func_array($this->_options['builder'], array($value, $record)); } /** * Generate the slug for a given Doctrine_Record slug field * * @param Doctrine_Record $record * @return string $slug */ protected function buildSlugFromSlugField($record) { $name = $record->getTable()->getFieldName($this->_options['name']); $value = $record->{$name}; if ($this->_options['unique'] === true) { return $this->getUniqueSlug($record, $value); } return call_user_func_array($this->_options['builder'], array($value, $record)); } /** * Creates a unique slug for a given Doctrine_Record. This function enforces the uniqueness by * incrementing the values with a postfix if the slug is not unique * * @param Doctrine_Record $record * @param string $slugFromFields * @return string $slug */ public function getUniqueSlug($record, $slugFromFields) { /* fix for use with Column Aggregation Inheritance */ if ($record->getTable()->getOption('inheritanceMap')) { $parentTable = $record->getTable()->getOption('parents'); $i = 0; // Be sure that you do not instanciate an abstract class; $reflectionClass = new ReflectionClass($parentTable[$i]); while ($reflectionClass->isAbstract()) { $i++; $reflectionClass = new ReflectionClass($parentTable[$i]); } $table = Doctrine_Core::getTable($parentTable[$i]); } else { $table = $record->getTable(); } $name = $table->getFieldName($this->_options['name']); $proposal = call_user_func_array($this->_options['builder'], array($slugFromFields, $record)); $slug = $proposal; $whereString = 'r.' . $name . ' LIKE ?'; $whereParams = array($proposal.'%'); if ($record->exists()) { $identifier = $record->identifier(); $whereString .= ' AND r.' . implode(' != ? AND r.', $table->getIdentifierColumnNames()) . ' != ?'; $whereParams = array_merge($whereParams, array_values($identifier)); } foreach ($this->_options['uniqueBy'] as $uniqueBy) { if (is_null($record->{$uniqueBy})) { $whereString .= ' AND r.'.$uniqueBy.' IS NULL'; } else { $whereString .= ' AND r.'.$uniqueBy.' = ?'; $value = $record->{$uniqueBy}; if ($value instanceof Doctrine_Record) { $value = current((array) $value->identifier()); } $whereParams[] = $value; } } // Disable indexby to ensure we get all records $originalIndexBy = $table->getBoundQueryPart('indexBy'); $table->bindQueryPart('indexBy', null); $query = $table->createQuery('r') ->select('r.' . $name) ->where($whereString , $whereParams) ->setHydrationMode(Doctrine_Core::HYDRATE_ARRAY); // We need to introspect SoftDelete to check if we are not disabling unique records too if ($table->hasTemplate('Doctrine_Template_SoftDelete')) { $softDelete = $table->getTemplate('Doctrine_Template_SoftDelete'); // we have to consider both situations here if ($softDelete->getOption('type') == 'boolean') { $conn = $query->getConnection(); $query->addWhere( '(r.' . $softDelete->getOption('name') . ' = ' . $conn->convertBooleans(true) . ' OR r.' . $softDelete->getOption('name') . ' = ' . $conn->convertBooleans(false) . ')' ); } else { $query->addWhere('(r.' . $softDelete->getOption('name') . ' IS NOT NULL OR r.' . $softDelete->getOption('name') . ' IS NULL)'); } } $similarSlugResult = $query->execute(); $query->free(); // Change indexby back $table->bindQueryPart('indexBy', $originalIndexBy); $similarSlugs = array(); foreach ($similarSlugResult as $key => $value) { $similarSlugs[$key] = strtolower($value[$name]); } $i = 1; while (in_array(strtolower($slug), $similarSlugs)) { $slug = call_user_func_array($this->_options['builder'], array($proposal.'-'.$i, $record)); $i++; } // If slug is longer then the column length then we need to trim it // and try to generate a unique slug again $length = $table->getFieldLength($this->_options['name']); if (strlen($slug) > $length) { $slug = substr($slug, 0, $length - (strlen($i) + 1)); $slug = $this->getUniqueSlug($record, $slug); } return $slug; } }