array( '‘' => '\'', // Translates to '‘'. '’' => '\'', // Translates to '’'. '‚' => '\'', // Translates to '‚'. '‛' => '\'', // Translates to '‛'. '“' => '"', // Translates to '“'. '”' => '"', // Translates to '”'. '„' => '"', // Translates to '„'. '‟' => '"', // Translates to '‟'. '«' => '"', // Translates to '«'. '»' => '"', // Translates to '»'. '‹' => '\'', // Translates to '«'. '›' => '\'', // Translates to '»'. ), 'out' => array( // Use the TypographyHelper for this at runtime. ), ); protected $_defaultConfig = array( 'before' => 'save', 'fields' => array(), 'mergeQuotes' => false, // Set to true for " or explicitly set a char (" or '). ); /** * Initiate behavior for the model using specified settings. * Available settings: * * @param object $Model Model using the behaviour * @param array $config Settings to override for model. * @return void */ public function setup(Model $Model, $config = array()) { if (!isset($this->settings[$Model->alias])) { $this->settings[$Model->alias] = $this->_defaultConfig; } $this->settings[$Model->alias] = array_merge($this->settings[$Model->alias], $config); if (empty($this->settings[$Model->alias]['fields'])) { $schema = $Model->schema(); $fields = array(); foreach ($schema as $field => $v) { if (!in_array($v['type'], array('string', 'text'))) { continue; } if (!empty($v['key'])) { continue; } if (isset($v['length']) && $v['length'] === 1) { // TODO: also skip UUID (lenght 36)? continue; } $fields[] = $field; } $this->settings[$Model->alias]['fields'] = $fields; } if ($this->settings[$Model->alias]['mergeQuotes'] === true) { $this->settings[$Model->alias]['mergeQuotes'] = '"'; } } /** * TypographicBehavior::beforeValidate() * * @param Model $Model * @return bool Success */ public function beforeValidate(Model $Model, $options = array()) { parent::beforeValidate($Model, $options); if ($this->settings[$Model->alias]['before'] === 'validate') { $this->process($Model); } return true; } /** * TypographicBehavior::beforeSave() * * @param Model $Model * @return bool Success */ public function beforeSave(Model $Model, $options = array()) { parent::beforeSave($Model, $options); if ($this->settings[$Model->alias]['before'] === 'save') { $this->process($Model); } return true; } /** * Run the behavior over all records of this model * This is useful if you attach it after some records have already been saved without it. * * @param Model $Model The model about to be saved. * @return int count Number of affected/changed records */ public function updateTypography(Model $Model, $dryRun = false) { $options = array('recursive' => -1, 'limit' => 100, 'offset' => 0); $count = 0; while ($records = $Model->find('all', $options)) { foreach ($records as $record) { $changed = false; foreach ($this->settings[$Model->alias]['fields'] as $field) { if (empty($record[$Model->alias][$field])) { continue; } $tmp = $this->_prepareInput($Model, $record[$Model->alias][$field]); if ($tmp == $record[$Model->alias][$field]) { continue; } $record[$Model->alias][$field] = $tmp; $changed = true; } if ($changed) { if (!$dryRun) { $Model->save($record, array('validate' => false)); } $count++; } } $options['offset'] += 100; } return $count; } /** * Run before a model is saved * * @param object $Model Model about to be saved. * @return bool true if save should proceed, false otherwise */ public function process(Model $Model, $return = true) { foreach ($this->settings[$Model->alias]['fields'] as $field) { if (!empty($Model->data[$Model->alias][$field])) { $Model->data[$Model->alias][$field] = $this->_prepareInput($Model, $Model->data[$Model->alias][$field]); } } return $return; } /** * @param string $input * @return string cleanedInput */ protected function _prepareInput(Model $Model, $string) { $map = $this->_map['in']; if ($this->settings[$Model->alias]['mergeQuotes']) { foreach ($map as $key => $val) { $map[$key] = $this->settings[$Model->alias]['mergeQuotes']; } } return str_replace(array_keys($map), array_values($map), $string); } }