PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/CMS/src/Model/Behavior/SluggableBehavior.php

http://github.com/QuickAppsCMS/QuickApps-CMS
PHP | 187 lines | 95 code | 21 blank | 71 comment | 12 complexity | 02cd0b7568e1cec1ce31f478fe7bac3a MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, GPL-3.0
  1. <?php
  2. /**
  3. * Licensed under The GPL-3.0 License
  4. * For full copyright and license information, please see the LICENSE.txt
  5. * Redistributions of files must retain the above copyright notice.
  6. *
  7. * @since 2.0.0
  8. * @author Christopher Castro <chris@quickapps.es>
  9. * @link http://www.quickappscms.org
  10. * @license http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License
  11. */
  12. namespace CMS\Model\Behavior;
  13. use Cake\Error\FatalErrorException;
  14. use Cake\Event\Event;
  15. use Cake\ORM\Behavior;
  16. use Cake\Utility\Inflector;
  17. /**
  18. * Sluggable Behavior.
  19. *
  20. * Allows entities to have a unique `slug`.
  21. */
  22. class SluggableBehavior extends Behavior
  23. {
  24. /**
  25. * Flag.
  26. *
  27. * @var bool
  28. */
  29. protected $_enabled = true;
  30. /**
  31. * Default configuration.
  32. *
  33. * - label: Set to the field name that contains the string from where to
  34. * generate the slug, or a set of field names to concatenate for
  35. * generating the slug. Defaults to `title`.
  36. * - slug: Name of the field name that holds generated slugs. Defaults to
  37. * `slug`.
  38. * - separator: Separator char. `-` by default. e.g.: `one-two-three`.
  39. * - on: When to generate new slugs. `create`, `update` or `both`. Defaults
  40. * to `both`.
  41. * - length: Maximum length the generated slug can have. default to 200.
  42. *
  43. * @var array
  44. */
  45. protected $_defaultConfig = [
  46. 'label' => 'title',
  47. 'slug' => 'slug',
  48. 'separator' => '-',
  49. 'on' => 'both',
  50. 'length' => 200,
  51. 'implementedMethods' => [
  52. 'bindSluggable' => 'bindSluggable',
  53. 'unbindSluggable' => 'unbindSluggable',
  54. ],
  55. ];
  56. /**
  57. * Run before a model is saved, used to set up slug for model.
  58. *
  59. * @param \Cake\Event\Event $event The event that was triggered
  60. * @param \Cake\ORM\Entity $entity The entity being saved
  61. * @param array $options Array of options for the save operation
  62. * @return bool True if save should proceed, false otherwise
  63. * @throws \Cake\Error\FatalErrorException When some of the specified columns
  64. * in config's "label" is not present in the entity being saved
  65. */
  66. public function beforeSave(Event $event, $entity, $options = [])
  67. {
  68. if (!$this->_enabled) {
  69. return true;
  70. }
  71. $config = $this->config();
  72. $isNew = $entity->isNew();
  73. if (($isNew && in_array($config['on'], ['create', 'both'])) ||
  74. (!$isNew && in_array($config['on'], ['update', 'both']))
  75. ) {
  76. if (!is_array($config['label'])) {
  77. $config['label'] = [$config['label']];
  78. }
  79. foreach ($config['label'] as $field) {
  80. if (!$entity->has($field)) {
  81. throw new FatalErrorException(__d('cms', 'SluggableBehavior was not able to generate a slug reason: entity\'s property "{0}" not found', $field));
  82. }
  83. }
  84. $label = '';
  85. foreach ($config['label'] as $field) {
  86. $val = $entity->get($field);
  87. $label .= !empty($val) ? " {$val}" : '';
  88. }
  89. if (!empty($label)) {
  90. $slug = $this->_slug($label, $entity);
  91. $entity->set($config['slug'], $slug);
  92. }
  93. }
  94. return true;
  95. }
  96. /**
  97. * Enables this behavior.
  98. *
  99. * @return void
  100. */
  101. public function bindSluggable()
  102. {
  103. $this->_enabled = true;
  104. }
  105. /**
  106. * Disables this behavior.
  107. *
  108. * @return void
  109. */
  110. public function unbindSluggable()
  111. {
  112. $this->_enabled = false;
  113. }
  114. /**
  115. * Generate a slug for the given string and entity. The generated slug is
  116. * unique across the entire table.
  117. *
  118. * @param string $string String from where to generate slug
  119. * @param \Cake\ORM\Entity $entity The entity for which generate the slug
  120. * @return string Slug for given string
  121. */
  122. protected function _slug($string, $entity)
  123. {
  124. $string = $this->_mbTrim(mb_strtolower($string));
  125. $config = $this->config();
  126. $slug = Inflector::slug($string, $config['separator']);
  127. $pk = $this->_table->primaryKey();
  128. if (mb_strlen($slug) > $config['length']) {
  129. $slug = mb_substr($slug, 0, $config['length']);
  130. }
  131. $conditions = ["{$config['slug']} LIKE" => "{$slug}%"];
  132. if ($entity->has($pk)) {
  133. $conditions["{$pk} NOT IN"] = [$entity->{$pk}];
  134. }
  135. $same = $this->_table->find()
  136. ->where($conditions)
  137. ->all()
  138. ->extract($config['slug'])
  139. ->toArray();
  140. if (!empty($same)) {
  141. $initialSlug = $slug;
  142. $index = 1;
  143. while ($index > 0) {
  144. $nextSlug = "{$initialSlug}{$config['separator']}{$index}";
  145. if (!in_array($nextSlug, $same)) {
  146. $slug = $nextSlug;
  147. $index = -1;
  148. }
  149. $index++;
  150. }
  151. }
  152. return $slug;
  153. }
  154. /**
  155. * Trim singlebyte and multibyte punctuation from the start and end of a
  156. * string.
  157. *
  158. * @param string $string Input string in UTF-8
  159. * @param string $trimChars Characters to trim off
  160. * @return trimmed string
  161. */
  162. protected function _mbTrim($string, $trimChars = '\s')
  163. {
  164. return preg_replace('/^[' . $trimChars . ']*(?U)(.*)[' . $trimChars . ']*$/u', '\\1', $string);
  165. }
  166. }