MODX Revolution PHP Cross Reference Content Management Systems

Source: /core/model/modx/modelement.class.php - 900 lines - 35192 bytes - Summary - Text - Print

   1  <?php
   2  /*
   3   * MODX Revolution
   4   *
   5   * Copyright 2006-2014 by MODX, LLC.
   6   * All rights reserved.
   7   *
   8   * This program is free software; you can redistribute it and/or modify it under
   9   * the terms of the GNU General Public License as published by the Free Software
  10   * Foundation; either version 2 of the License, or (at your option) any later
  11   * version.
  12   *
  13   * This program is distributed in the hope that it will be useful, but WITHOUT
  14   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  16   * details.
  17   *
  18   * You should have received a copy of the GNU General Public License along with
  19   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  20   * Place, Suite 330, Boston, MA 02111-1307 USA
  21   */
  22  /**
  23   * Represents an element of source content managed by MODX.
  24   *
  25   * These elements are defined by some type of source content that when processed
  26   * will provide output or some type of logical result based on mutable
  27   * properties.
  28   *
  29   * This class creates an instance of a modElement object. This should not be
  30   * called directly, but rather extended for derivative modElement classes.
  31   *
  32   * @property modMediaSource $Source The associated Media Source, if any.
  33   *
  34   * @package modx
  35   * @abstract Implement a derivative of this class to represent an element which
  36   * can be processed within the MODX framework.
  37   * @extends modAccessibleSimpleObject
  38   */
  39  class modElement extends modAccessibleSimpleObject {
  40      /**
  41       * The property value array for the element.
  42       * @var array
  43       */
  44      public $_properties= null;
  45      /**
  46       * The string representation of the element properties.
  47       * @var string
  48       */
  49      public $_propertyString= '';
  50      /**
  51       * The source content of the element.
  52       * @var string
  53       */
  54      public $_content= '';
  55      /**
  56       * The source of the element.
  57       * @var string
  58       */
  59      public $_source= null;
  60      /**
  61       * The output of the element.
  62       * @var string
  63       */
  64      public $_output= '';
  65      /**
  66       * The boolean result of the element.
  67       *
  68       * This is typically only applicable to elements that use PHP source content.
  69       * @var boolean
  70       */
  71      public $_result= true;
  72      /**
  73       * The tag signature of the element instance.
  74       * @var string
  75       */
  76      public $_tag= null;
  77      /**
  78       * The character token which helps identify the element class in tag string.
  79       * @var string
  80       */
  81      public $_token= '';
  82      /**
  83       * @var boolean If the element is cacheable or not.
  84       */
  85      public $_cacheable= true;
  86      /**
  87       * @var boolean Indicates if the element was processed already.
  88       */
  89      public $_processed= false;
  90      /**
  91       * @var array Optional filters that can be used during processing.
  92       */
  93      public $_filters= array('input' => null, 'output' => null);
  94  
  95      /**
  96       * @var string Path to source file location when modElement->isStatic() === true.
  97       */
  98      protected $_sourcePath= "";
  99      /**
 100       * @var string Source file name when modElement->isStatic() === true.
 101       */
 102      protected $_sourceFile= "";
 103      /**
 104       * @var array A list of invalid characters in the name of an Element.
 105       */
 106      protected $_invalidCharacters = array('!','@','#','$','%','^','&','*',
 107      '(',')','+','=','[',']','{','}','\'','"',';',':','\\','/','<','>','?'
 108      ,' ',',','`','~');
 109  
 110      /**
 111       * Provides custom handling for retrieving the properties field of an Element.
 112       *
 113       * {@inheritdoc}
 114       */
 115      public function get($k, $format= null, $formatTemplate= null) {
 116          $value = parent :: get($k, $format, $formatTemplate);
 117          if ($k === 'properties' && $this->xpdo instanceof modX && $this->xpdo->getParser() && empty($value)) {
 118              $value = !empty($this->properties) && is_string($this->properties)
 119                  ? $this->xpdo->parser->parsePropertyString($this->properties)
 120                  : null;
 121          }
 122          /* automatically translate lexicon descriptions */
 123          if ($k == 'properties' && !empty($value) && is_array($value)
 124                 && is_object($this->xpdo) && $this->xpdo instanceof modX && $this->xpdo->lexicon) {
 125              foreach ($value as &$property) {
 126                  if (!empty($property['lexicon'])) {
 127                      if (strpos($property['lexicon'],':') !== false) {
 128                          $this->xpdo->lexicon->load('en:'.$property['lexicon']);
 129                      } else {
 130                          $this->xpdo->lexicon->load('en:core:'.$property['lexicon']);
 131                      }
 132                      $this->xpdo->lexicon->load($property['lexicon']);
 133                  }
 134                  $property['desc_trans'] = $this->xpdo->lexicon($property['desc']);
 135                  $property['area'] = !empty($property['area']) ? $property['area'] : '';
 136                  $property['area_trans'] = $this->xpdo->lexicon($property['area']);
 137  
 138                  if (!empty($property['options'])) {
 139                      foreach ($property['options'] as &$option) {
 140                          if (empty($option['text']) && !empty($option['name'])) {
 141                              $option['text'] = $option['name'];
 142                              unset($option['name']);
 143                          }
 144                          if (empty($option['value']) && !empty($option[0])) {
 145                              $option['value'] = $option[0];
 146                              unset($option[0]);
 147                          }
 148                          $option['name'] = $this->xpdo->lexicon($option['text']);
 149                      }
 150                  }
 151              }
 152          }
 153          return $value;
 154      }
 155  
 156      /**
 157       * Overridden to handle changes to content managed in an external file.
 158       *
 159       * {@inheritdoc}
 160       */
 161      public function save($cacheFlag = null) {
 162          if (!$this->getOption(xPDO::OPT_SETUP)) {
 163              if ($this->staticSourceChanged()) {
 164                  $staticContent = $this->getFileContent();
 165                  if ($staticContent !== $this->get('content')) {
 166                      if ($this->isStaticSourceMutable() && $staticContent === '') {
 167                          $this->setDirty('content');
 168                      } else {
 169                          $this->setContent($staticContent);
 170                      }
 171                  }
 172                  unset($staticContent);
 173              }
 174              $staticContentChanged = $this->staticContentChanged();
 175              if ($staticContentChanged && !$this->isStaticSourceMutable()) {
 176                  $this->setContent($this->getFileContent());
 177                  $staticContentChanged = false;
 178              }
 179          }
 180          $saved = parent::save($cacheFlag);
 181          if (!$this->getOption(xPDO::OPT_SETUP)) {
 182              if ($saved && $staticContentChanged) {
 183                  $saved = $this->setFileContent($this->get('content'));
 184              }
 185          }
 186          return $saved;
 187      }
 188  
 189      /**
 190       * Constructs a valid tag representation of the element.
 191       *
 192       * @return string A tag representation of the element.
 193       */
 194      public function getTag() {
 195          if (empty($this->_tag)) {
 196              $propTemp = array();
 197              if (empty($this->_propertyString) && !empty($this->_properties)) {
 198                  while(list($key, $value) = each($this->_properties)) {
 199                      if (is_scalar($value)) {
 200                          $propTemp[] = trim($key) . '=`' . $value . '`';
 201                      }
 202                      else {
 203                          $propTemp[] = trim($key) . '=`' . md5(uniqid(rand(), true)) . '`';
 204                      }
 205                  }
 206                  if (!empty($propTemp)) {
 207                      $this->_propertyString = '?' . implode('&', $propTemp);
 208                  }
 209              }
 210              $tag = '[[';
 211              $tag.= $this->getToken();
 212              $tag.= $this->get('name');
 213              if (!empty($this->_propertyString)) {
 214                  $tag.= $this->_propertyString;
 215              }
 216              $tag.= ']]';
 217              $this->setTag($tag);
 218          }
 219          if (empty($this->_tag)) {
 220              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Instance of ' . get_class($this) . ' produced an empty tag!');
 221          }
 222          return $this->_tag;
 223      }
 224  
 225      /**
 226       * Accessor method for the token class var.
 227       *
 228       * @return string The token for this element tag.
 229       */
 230      public function getToken() {
 231          return $this->_token;
 232      }
 233  
 234      /**
 235       * Setter method for the token class var.
 236       *
 237       * @param string $token The token to use for this element tag.
 238       */
 239      public function setToken($token) {
 240          $this->_token = $token;
 241      }
 242  
 243      /**
 244       * Setter method for the tag class var.
 245       *
 246       * @param string $tag The tag to use for this element.
 247       */
 248      public function setTag($tag) {
 249          $this->_tag = $tag;
 250      }
 251  
 252  
 253      /**
 254       * Process the element source content to produce a result.
 255       *
 256       * @abstract Implement this to define behavior for a MODX content element.
 257       * @param array|string $properties A set of configuration properties for the
 258       * element.
 259       * @param string $content Optional content to use in place of any persistent
 260       * content associated with the element.
 261       * @return mixed The result of processing.
 262       */
 263      public function process($properties= null, $content= null) {
 264          $this->xpdo->getParser();
 265          $this->xpdo->parser->setProcessingElement(true);
 266          $this->getProperties($properties);
 267          $this->getTag();
 268          if ($this->xpdo->getDebug() === true) $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Processing Element: " . $this->get('name') . ($this->_tag ? "\nTag: {$this->_tag}" : "\n") . "\nProperties: " . print_r($this->_properties, true));
 269          if ($this->isCacheable() && isset ($this->xpdo->elementCache[$this->_tag])) {
 270              $this->_output = $this->xpdo->elementCache[$this->_tag];
 271              $this->_processed = true;
 272          } else {
 273              $this->filterInput();
 274              $this->getContent(is_string($content) ? array('content' => $content) : array());
 275          }
 276          return $this->_result;
 277      }
 278  
 279      /**
 280       * Cache the current output of this element instance by tag signature.
 281       */
 282      public function cache() {
 283          if ($this->isCacheable()) {
 284              $this->xpdo->elementCache[$this->_tag]= $this->_output;
 285          }
 286      }
 287  
 288      /**
 289       * Get an input filter instance configured for this Element.
 290       *
 291       * @return modInputFilter|null An input filter instance (or null if one cannot be loaded).
 292       */
 293      public function & getInputFilter() {
 294          if (!isset ($this->_filters['input']) || !($this->_filters['input'] instanceof modInputFilter)) {
 295              if (!$inputFilterClass= $this->get('input_filter')) {
 296                  $inputFilterClass = $this->xpdo->getOption('input_filter',null,'filters.modInputFilter');
 297              }
 298              if ($filterClass= $this->xpdo->loadClass($inputFilterClass, '', false, true)) {
 299                  if ($filter= new $filterClass($this->xpdo)) {
 300                      $this->_filters['input']= $filter;
 301                  }
 302              }
 303          }
 304          return $this->_filters['input'];
 305      }
 306  
 307      /**
 308       * Get an output filter instance configured for this Element.
 309       *
 310       * @return modOutputFilter|null An output filter instance (or null if one cannot be loaded).
 311       */
 312      public function & getOutputFilter() {
 313          if (!isset ($this->_filters['output']) || !($this->_filters['output'] instanceof modOutputFilter)) {
 314              if (!$outputFilterClass= $this->get('output_filter')) {
 315                  $outputFilterClass = $this->xpdo->getOption('output_filter',null,'filters.modOutputFilter');
 316              }
 317              if ($filterClass= $this->xpdo->loadClass($outputFilterClass, '', false, true)) {
 318                  if ($filter= new $filterClass($this->xpdo)) {
 319                      $this->_filters['output']= $filter;
 320                  }
 321              }
 322          }
 323          return $this->_filters['output'];
 324      }
 325  
 326      /**
 327       * Apply an input filter to an element.
 328       *
 329       * This is called by default in {@link modElement::process()} after the
 330       * element properties have been parsed.
 331       */
 332      public function filterInput() {
 333          $filter = $this->getInputFilter();
 334          if ($filter !== null && $filter instanceof modInputFilter) {
 335              $filter->filter($this);
 336          }
 337      }
 338  
 339      /**
 340       * Apply an output filter to an element.
 341       *
 342       * Call this method in your {modElement::process()} implementation when it
 343       * is appropriate, typically once all processing has been completed, but
 344       * before any caching takes place.
 345       */
 346      public function filterOutput() {
 347          $filter = $this->getOutputFilter();
 348          if ($filter !== null && $filter instanceof modOutputFilter) {
 349              $filter->filter($this);
 350          }
 351      }
 352  
 353      /**
 354       * Loads the access control policies applicable to this element.
 355       *
 356       * {@inheritdoc}
 357       */
 358      public function findPolicy($context = '') {
 359          $policy = array();
 360          $enabled = true;
 361          $context = !empty($context) ? $context : $this->xpdo->context->get('key');
 362          if ($context === $this->xpdo->context->get('key')) {
 363              $enabled = (boolean) $this->xpdo->getOption('access_category_enabled', null, true);
 364          } elseif ($this->xpdo->getContext($context)) {
 365              $enabled = (boolean) $this->xpdo->contexts[$context]->getOption('access_category_enabled', true);
 366          }
 367          if ($enabled) {
 368              if (empty($this->_policies) || !isset($this->_policies[$context])) {
 369                  $accessTable = $this->xpdo->getTableName('modAccessCategory');
 370                  $policyTable = $this->xpdo->getTableName('modAccessPolicy');
 371                  $categoryClosureTable = $this->xpdo->getTableName('modCategoryClosure');
 372                  $sql = "SELECT Acl.target, Acl.principal, Acl.authority, Acl.policy, Policy.data FROM {$accessTable} Acl " .
 373                          "LEFT JOIN {$policyTable} Policy ON Policy.id = Acl.policy " .
 374                          "JOIN {$categoryClosureTable} CategoryClosure ON CategoryClosure.descendant = :category " .
 375                          "AND Acl.principal_class = 'modUserGroup' " .
 376                          "AND CategoryClosure.ancestor = Acl.target " .
 377                          "AND (Acl.context_key = :context OR Acl.context_key IS NULL OR Acl.context_key = '') " .
 378                          "ORDER BY CategoryClosure.depth DESC, target, principal, authority ASC";
 379                  $bindings = array(
 380                      ':category' => $this->get('category'),
 381                      ':context' => $context,
 382                  );
 383                  $query = new xPDOCriteria($this->xpdo, $sql, $bindings);
 384                  if ($query->stmt && $query->stmt->execute()) {
 385                      while ($row = $query->stmt->fetch(PDO::FETCH_ASSOC)) {
 386                          $policy['modAccessCategory'][$row['target']][] = array(
 387                              'principal' => $row['principal'],
 388                              'authority' => $row['authority'],
 389                              'policy' => $row['data'] ? $this->xpdo->fromJSON($row['data'], true) : array(),
 390                          );
 391                      }
 392                  }
 393                  $this->_policies[$context] = $policy;
 394              } else {
 395                  $policy = $this->_policies[$context];
 396              }
 397          }
 398          return $policy;
 399      }
 400  
 401      /**
 402       * Gets the raw, unprocessed source content for this element.
 403       *
 404       * @param array $options An array of options implementations can use to
 405       * accept language, revision identifiers, or other information to alter the
 406       * behavior of the method.
 407       * @return string The raw source content for the element.
 408       */
 409      public function getContent(array $options = array()) {
 410          if (!is_string($this->_content) || $this->_content === '') {
 411              if (isset($options['content'])) {
 412                  $this->_content = $options['content'];
 413              } elseif ($this->isStatic()) {
 414                  $this->_content = $this->getFileContent($options);
 415                  if ($this->_content !== $this->_fields['content']) {
 416                      $this->setContent($this->_content);
 417                      if (!$this->isNew()) {
 418                          $this->save();
 419                      }
 420                  }
 421              } else {
 422                  $this->_content = $this->get('content');
 423              }
 424          }
 425          return $this->_content;
 426      }
 427  
 428      /**
 429       * Set the raw source content for this element.
 430       *
 431       * @param mixed $content The source content; implementations can decide if
 432       * it can only be a string, or some other source from which to retrieve it.
 433       * @param array $options An array of options implementations can use to
 434       * accept language, revision identifiers, or other information to alter the
 435       * behavior of the method.
 436       * @return boolean True indicates the content was set.
 437       */
 438      public function setContent($content, array $options = array()) {
 439          return $this->set('content', $content);
 440      }
 441  
 442      /**
 443       * Get the absolute path to the static source file for this instance.
 444       *
 445       * @param array $options An array of options.
 446       * @return string|boolean The absolute path to the static source file or false if not static.
 447       */
 448      public function getSourceFile(array $options = array()) {
 449          if ($this->isStatic() && (empty($this->_sourceFile) || $this->getOption('recalculate_source_file', $options, $this->staticSourceChanged()))) {
 450              $filename = $this->get('static_file');
 451              if (!empty($filename)) {
 452                  $array = array();
 453                  if ($this->xpdo->getParser() && $this->xpdo->parser->collectElementTags($filename, $array)) {
 454                      $this->xpdo->parser->processElementTags('', $filename);
 455                  }
 456              }
 457  
 458              if ($this->get('source') > 0) {
 459                  /** @var modMediaSource $source */
 460                  $source = $this->getOne('Source');
 461                  if ($source && $source->get('is_stream')) {
 462                      $source->initialize();
 463                      $filename = $source->getBasePath().$filename;
 464                  }
 465              }
 466  
 467              if (!file_exists($filename) && $this->get('source') < 1) {
 468                  $this->getSourcePath($options);
 469                  $this->_sourceFile= $this->_sourcePath . $filename;
 470              } else {
 471                  $this->_sourceFile= $filename;
 472              }
 473          }
 474          return $this->isStatic() ? $this->_sourceFile : false;
 475      }
 476  
 477      /**
 478       * Get the absolute path location the source file is located relative to.
 479       *
 480       * @param array $options An array of options.
 481       * @return string The absolute path the sourceFile is relative to.
 482       */
 483      public function getSourcePath(array $options = array()) {
 484          $array = array();
 485          $this->_sourcePath= $this->xpdo->getOption('element_static_path', $options, $this->xpdo->getOption('components_path', $options, MODX_CORE_PATH . 'components/'));
 486          if ($this->xpdo->getParser() && $this->xpdo->parser->collectElementTags($this->_sourcePath, $array)) {
 487              $this->xpdo->parser->processElementTags('', $this->_sourcePath);
 488          }
 489          return $this->_sourcePath;
 490      }
 491  
 492      /**
 493       * Get the content stored in an external file for this instance.
 494       *
 495       * @param array $options An array of options.
 496       * @return bool|string The content or false if the content could not be retrieved.
 497       */
 498      public function getFileContent(array $options = array()) {
 499          $content = "";
 500          if ($this->isStatic()) {
 501              $sourceFile = $this->getSourceFile($options);
 502              if ($sourceFile && file_exists($sourceFile)) {
 503                  $content = file_get_contents($sourceFile);
 504              }
 505          }
 506          return $content;
 507      }
 508  
 509      /**
 510       * Set external file content from this instance.
 511       *
 512       * @param string $content The content to set.
 513       * @param array $options An array of options.
 514       * @return bool|int The number of bytes written to file or false on failure.
 515       */
 516      public function setFileContent($content, array $options = array()) {
 517          $set = false;
 518          if ($this->isStatic()) {
 519              $sourceFile = $this->getSourceFile($options);
 520              if ($sourceFile) {
 521                  $set = $this->xpdo->cacheManager->writeFile($sourceFile, $content);
 522              }
 523          }
 524          return $set;
 525      }
 526  
 527      /**
 528       * Get the properties for this element instance for processing.
 529       *
 530       * @param array|string $properties An array or string of properties to
 531       * apply.
 532       * @return array A simple array of properties ready to use for processing.
 533       */
 534      public function getProperties($properties = null) {
 535          $this->xpdo->getParser();
 536          $this->_properties= $this->xpdo->parser->parseProperties($this->get('properties'));
 537          $set= $this->getPropertySet();
 538          if (!empty($set)) {
 539              $this->_properties= array_merge($this->_properties, $set);
 540          }
 541          if ($this->get('property_preprocess')) {
 542              foreach ($this->_properties as $pKey => $pValue) {
 543                  if ($this->xpdo->parser->processElementTags('', $pValue, $this->xpdo->parser->isProcessingUncacheable())) {
 544                      $this->_properties[$pKey]= $pValue;
 545                  }
 546              }
 547          }
 548          if (!empty($properties)) {
 549              $this->_properties= array_merge($this->_properties, $this->xpdo->parser->parseProperties($properties));
 550          }
 551          return $this->_properties;
 552      }
 553  
 554      /**
 555       * Gets a named property set related to this element instance.
 556       *
 557       * If a setName parameter is not provided, this function will attempt to
 558       * extract a setName from the element name using the @ symbol to delimit the
 559       * name of the property set.
 560       *
 561       * Here is an example of an element tag using the @ modifier to specify a
 562       * property set name:
 563       *  [[ElementName@PropertySetName:FilterCommand=`FilterModifier`?
 564       *      &PropertyKey1=`PropertyValue1`
 565       *      &PropertyKey2=`PropertyValue2`
 566       *  ]]
 567       *
 568       * @access public
 569       * @param string|null $setName An explicit property set name to search for.
 570       * @return array|null An array of properties or null if no set is found.
 571       */
 572      public function getPropertySet($setName = null) {
 573          $propertySet= null;
 574          $name = $this->get('name');
 575          if (strpos($name, '@') !== false) {
 576              $psName= '';
 577              $split= xPDO :: escSplit('@', $name);
 578              if ($split && isset($split[1])) {
 579                  $name= $split[0];
 580                  $psName= $split[1];
 581                  $filters= xPDO :: escSplit(':', $setName);
 582                  if ($filters && isset($filters[1]) && !empty($filters[1])) {
 583                      $psName= $filters[0];
 584                      $name.= ':' . $filters[1];
 585                  }
 586                  $this->set('name', $name);
 587              }
 588              if (!empty($psName)) {
 589                  $psObj= $this->xpdo->getObjectGraph('modPropertySet', '{"Elements":{}}', array(
 590                      'Elements.element' => $this->id,
 591                      'Elements.element_class' => $this->_class,
 592                      'modPropertySet.name' => $psName
 593                  ));
 594                  if ($psObj) {
 595                      $propertySet= $this->xpdo->parser->parseProperties($psObj->get('properties'));
 596                  }
 597              }
 598          }
 599          if (!empty($setName)) {
 600              $propertySetObj= $this->xpdo->getObjectGraph('modPropertySet', '{"Elements":{}}', array(
 601                  'Elements.element' => $this->id,
 602                  'Elements.element_class' => $this->_class,
 603                  'modPropertySet.name' => $setName
 604              ));
 605              if ($propertySetObj) {
 606                  if (is_array($propertySet)) {
 607                      $propertySet= array_merge($propertySet, $this->xpdo->parser->parseProperties($propertySetObj->get('properties')));
 608                  } else {
 609                      $propertySet= $this->xpdo->parser->parseProperties($propertySetObj->get('properties'));
 610                  }
 611              }
 612          }
 613          return $propertySet;
 614      }
 615  
 616      /**
 617       * Set default properties for this element instance.
 618       *
 619       * @access public
 620       * @param array|string $properties A property array or property string.
 621       * @param boolean $merge Indicates if properties should be merged with
 622       * existing ones.
 623       * @return boolean true if the properties are set.
 624       */
 625      public function setProperties($properties, $merge = false) {
 626          $set = false;
 627          $propertiesArray = array();
 628          if (is_string($properties)) {
 629              $properties = $this->xpdo->parser->parsePropertyString($properties);
 630          }
 631          if (is_array($properties)) {
 632              foreach ($properties as $propKey => $property) {
 633                  if (is_array($property) && isset($property[5])) {
 634                      $key = $property[0];
 635                      $propertyArray = array(
 636                          'name' => $property[0],
 637                          'desc' => $property[1],
 638                          'type' => $property[2],
 639                          'options' => $property[3],
 640                          'value' => $property[4],
 641                          'lexicon' => !empty($property[5]) ? $property[5] : null,
 642                          'area' => !empty($property[6]) ? $property[6] : '',
 643                      );
 644                  } elseif (is_array($property) && isset($property['value'])) {
 645                      $key = $property['name'];
 646                      $propertyArray = array(
 647                          'name' => $property['name'],
 648                          'desc' => isset($property['description']) ? $property['description'] : (isset($property['desc']) ? $property['desc'] : ''),
 649                          'type' => isset($property['xtype']) ? $property['xtype'] : (isset($property['type']) ? $property['type'] : 'textfield'),
 650                          'options' => isset($property['options']) ? $property['options'] : array(),
 651                          'value' => $property['value'],
 652                          'lexicon' => !empty($property['lexicon']) ? $property['lexicon'] : null,
 653                          'area' => !empty($property['area']) ? $property['area'] : '',
 654                      );
 655                  } else {
 656                      $key = $propKey;
 657                      $propertyArray = array(
 658                          'name' => $propKey,
 659                          'desc' => '',
 660                          'type' => 'textfield',
 661                          'options' => array(),
 662                          'value' => $property,
 663                          'lexicon' => null,
 664                          'area' => '',
 665                      );
 666                  }
 667  
 668                  if (!empty($propertyArray['options'])) {
 669                      foreach ($propertyArray['options'] as $optionKey => &$option) {
 670                          if (empty($option['text']) && !empty($option['name'])) $option['text'] = $option['name'];
 671                          unset($option['menu'],$option['name']);
 672                      }
 673                  }
 674  
 675                  if ($propertyArray['type'] == 'combo-boolean' && is_numeric($propertyArray['value'])) {
 676                      $propertyArray['value'] = (boolean)$propertyArray['value'];
 677                  }
 678  
 679                  $propertiesArray[$key] = $propertyArray;
 680              }
 681  
 682              if ($merge && !empty($propertiesArray)) {
 683                  $existing = $this->get('properties');
 684                  if (is_array($existing) && !empty($existing)) {
 685                      $propertiesArray = array_merge($existing, $propertiesArray);
 686                  }
 687              }
 688              $set = $this->set('properties', $propertiesArray);
 689          }
 690          return $set;
 691      }
 692  
 693      /**
 694       * Add a property set to this element, making it available for use.
 695       *
 696       * @access public
 697       * @param string|modPropertySet $propertySet A modPropertySet object or the
 698       * name of a modPropertySet object to create a relationship with.
 699       * @return boolean True if a relationship was created or already exists.
 700       */
 701      public function addPropertySet($propertySet) {
 702          $added= false;
 703          if (!empty($propertySet)) {
 704              if (is_string($propertySet)) {
 705                  $propertySet = $this->xpdo->getObject('modPropertySet', array('name' => $propertySet));
 706              }
 707              if (is_object($propertySet) && $propertySet instanceof modPropertySet) {
 708                  if (!$this->isNew() && !$propertySet->isNew() && $this->xpdo->getCount('modElementPropertySet', array('element' => $this->get('id'), 'element_class' => $this->_class, 'property_set' => $propertySet->get('id')))) {
 709                      $added = true;
 710                  } else {
 711                      if ($propertySet->isNew()) $propertySet->save();
 712                      /** @var modElementPropertySet $link */
 713                      $link= $this->xpdo->newObject('modElementPropertySet');
 714                      $link->set('element', $this->get('id'));
 715                      $link->set('element_class', $this->_class);
 716                      $link->set('property_set', $propertySet->get('id'));
 717                      $added = $link->save();
 718                  }
 719              }
 720          }
 721          return $added;
 722      }
 723  
 724      /**
 725       * Remove a property set from this element, making it unavailable for use.
 726       *
 727       * @access public
 728       * @param string|modPropertySet $propertySet A modPropertySet object or the
 729       * name of a modPropertySet object to dissociate from.
 730       * @return boolean True if a relationship was destroyed.
 731       */
 732      public function removePropertySet($propertySet) {
 733          $removed = false;
 734          if (!empty($propertySet)) {
 735              if (is_string($propertySet)) {
 736                  $propertySet = $this->xpdo->getObject('modPropertySet', array('name' => $propertySet));
 737              }
 738              if (is_object($propertySet) && $propertySet instanceof modPropertySet) {
 739                  $removed = $this->xpdo->removeObject('modElementPropertySet', array('element' => $this->get('id'), 'element_class' => $this->_class, 'property_set' => $propertySet->get('id')));
 740              }
 741          }
 742          return $removed;
 743      }
 744  
 745      /**
 746       * Indicates if the element is cacheable.
 747       *
 748       * @access public
 749       * @return boolean True if the element can be stored to or retrieved from
 750       * the element cache.
 751       */
 752      public function isCacheable() {
 753          return $this->_cacheable;
 754      }
 755  
 756      /**
 757       * Sets the runtime cacheability of the element.
 758       *
 759       * @access public
 760       * @param boolean $cacheable Indicates the value to set for cacheability of
 761       * this element.
 762       */
 763      public function setCacheable($cacheable = true) {
 764          $this->_cacheable = (boolean) $cacheable;
 765      }
 766  
 767      /**
 768       * Get the Source for this Element
 769       *
 770       * @param string $contextKey
 771       * @param boolean $fallbackToDefault
 772       * @return modMediaSource|null
 773       */
 774      public function getSource($contextKey = '',$fallbackToDefault = true) {
 775          if (empty($contextKey)) $contextKey = $this->xpdo->context->get('key');
 776  
 777          $source = $this->_source;
 778  
 779          if (empty($source)) {
 780  
 781              $c = $this->xpdo->newQuery('sources.modMediaSource');
 782              $c->innerJoin('sources.modMediaSourceElement','SourceElement');
 783              $c->where(array(
 784                  'SourceElement.object' => $this->get('id'),
 785                  'SourceElement.object_class' => $this->_class,
 786                  'SourceElement.context_key' => $contextKey,
 787              ));
 788              $source = $this->xpdo->getObject('sources.modMediaSource',$c);
 789              if (!$source && $fallbackToDefault) {
 790                  $source = modMediaSource::getDefaultSource($this->xpdo);
 791              }
 792              $this->setSource($source);
 793          }
 794  
 795          return $source;
 796      }
 797  
 798      /**
 799       * Setter method for the source class var.
 800       *
 801       * @param string $source The source to use for this element.
 802       */
 803      public function setSource($source) {
 804          $this->_source = $source;
 805      }
 806  
 807      /**
 808       * Get the stored sourceCache for a context
 809       *
 810       * @param string $contextKey
 811       * @param array $options
 812       * @return array
 813       */
 814      public function getSourceCache($contextKey = '',array $options = array()) {
 815          /** @var modCacheManager $cacheManager */
 816          $cacheManager = $this->xpdo->getCacheManager();
 817          if (!$cacheManager || !($cacheManager instanceof modCacheManager)) return array();
 818  
 819          if (empty($contextKey)) $contextKey = $this->xpdo->context->get('key');
 820  
 821          return $cacheManager->getElementMediaSourceCache($this,$contextKey,$options);
 822      }
 823  
 824      /**
 825       * Indicates if the instance has content in an external file.
 826       *
 827       * @return boolean True if the instance has content stored in an external file.
 828       */
 829      public function isStatic() {
 830          return $this->get('static');
 831      }
 832  
 833      /**
 834       * Indicates if the content has changed and the Element has a mutable static source.
 835       *
 836       * @return boolean
 837       */
 838      public function staticContentChanged() {
 839          return $this->isStatic() && $this->isDirty('content');
 840      }
 841  
 842      /**
 843       * Indicates if the static source has changed.
 844       *
 845       * @return boolean
 846       */
 847      public function staticSourceChanged() {
 848          return $this->isStatic() && ($this->isDirty('static') || $this->isDirty('static_file') || $this->isDirty('source'));
 849      }
 850  
 851      /**
 852       * Return if the static source is mutable.
 853       *
 854       * @return boolean True if the source file is mutable.
 855       */
 856      public function isStaticSourceMutable() {
 857          $isMutable = false;
 858          $sourceFile = $this->getSourceFile();
 859          if ($sourceFile) {
 860              if (file_exists($sourceFile)) {
 861                  $isMutable = is_writable($sourceFile) && !is_dir($sourceFile);
 862              } else {
 863                  $sourceDir = dirname($sourceFile);
 864                  $i = 100;
 865                  while (!empty($sourceDir)) {
 866                      if (file_exists($sourceDir) && is_dir($sourceDir)) {
 867                          $isMutable = is_writable($sourceDir);
 868                          if ($isMutable) break;
 869                      }
 870                      if ($sourceDir != '/') {
 871                          $sourceDir = dirname($sourceDir);
 872                      } else {
 873                          break;
 874                      }
 875                      $i--;
 876                      if ($i < 0) break;
 877                  }
 878              }
 879          }
 880          return $isMutable;
 881      }
 882  
 883      /**
 884       * Ensure the static source cannot browse the protected configuration directory
 885       *
 886       * @return boolean True if is a valid source path
 887       */
 888      public function isStaticSourceValidPath() {
 889          $isValid = true;
 890          $sourceFile = $this->getSourceFile();
 891          if ($sourceFile) {
 892              $sourceDirectory = rtrim(dirname($sourceFile),'/');
 893              $configDirectory = rtrim($this->xpdo->getOption('core_path',null,MODX_CORE_PATH).'config/','/');
 894              if ($sourceDirectory == $configDirectory) {
 895                  $isValid = false;
 896              }
 897          }
 898          return $isValid;
 899      }
 900  }

title

Description

title

Description

title

Description

title

title

Body