core/model/modx/processors/resource/create.class.php
<?php
/*
* This file is part of MODX Revolution.
*
* Copyright (c) MODX, LLC. All Rights Reserved.
*
* For complete copyright and license information, see the COPYRIGHT and LICENSE
* files found in the top-level directory of this distribution.
*/
/**
* Creates a resource.
*
* @param string $pagetitle The page title.
* @param string $content The HTML content. Used in conjunction with $ta.
* @param integer $template (optional) The modTemplate to use with this
* resource. Defaults to 0, or a blank template.
* @param integer $parent (optional) The parent resource ID. Defaults to 0.
* @param string $class_key (optional) The class key. Defaults to modDocument.
* @param integer $menuindex (optional) The menu order. Defaults to 0.
* @param string $variablesmodified (optional) A collection of modified TVs.
* Along with $tv1, $tv2, etc.
* @param string $context_key (optional) The context in which this resource is
* located. Defaults to web.
* @param string $alias (optional) The alias for FURLs that this resource is
* designated to.
* @param integer $content_type (optional) The content type. Defaults to
* text/html.
* @param boolean $published (optional) The published status.
* @param string $pub_date (optional) The date on which this resource should
* become published.
* @param string $unpub_date (optional) The date on which this resource should
* become unpublished.
* @param string $publishedon (optional) The date this resource was published.
* Defaults to time()
* @param integer $publishedby (optional) The modUser who published this
* resource. Defaults to the current user.
* @param json $resource_groups (optional) A JSON array of resource groups to
* assign this resource to.
* @param boolean $hidemenu (optional) If true, The resource will not show up in
* menu builders.
* @param boolean $isfolder (optional) Whether or not the resource is a
* container of resources.
* @param boolean $richtext (optional) If true, MODX will render the available
* RTE for editing this resource.
* @param boolean $donthit (optional) (deprecated) If true, MODX will not log
* visits on this resource.
* @param boolean $cacheable (optional) If false, the resource will not be
* cached.
* @param boolean $searchable (optional) If false, the resource will not appear
* in searches.
* @param boolean $syncsite (optional) If false, will not empty the cache on
* save.
*
* @package modx
* @subpackage processors.resource
*/
class modResourceCreateProcessor extends modObjectCreateProcessor {
public $classKey = 'modResource';
public $languageTopics = array('resource');
public $permission = 'new_document';
public $objectType = 'resource';
public $beforeSaveEvent = 'OnBeforeDocFormSave';
public $afterSaveEvent = 'OnDocFormSave';
/** @var modResource $parentResource */
public $parentResource;
/** @var modContext $this->workingContext */
public $workingContext;
/** @var modTemplate $template */
public $template;
/** @var modResource $object */
public $object;
/**
* Allow for Resources to use derivative classes for their processors
*
* @static
* @param modX $modx
* @param $className
* @param array $properties
* @return modProcessor
*/
public static function getInstance(modX &$modx,$className,$properties = array()) {
$classKey = !empty($properties['class_key']) ? $properties['class_key'] : 'modDocument';
$object = $modx->newObject($classKey);
if (!in_array($classKey,array('modDocument','modResource',''))) {
$className = $classKey.'CreateProcessor';
if (!class_exists($className)) {
$className = 'modResourceCreateProcessor';
}
}
/** @var modProcessor $processor */
$processor = new $className($modx,$properties);
return $processor;
}
public function checkPermissions()
{
$permissions = [$this->permission];
// Get and normalise the class key to avoid bypassing checks with different capitalization
$classKey = $this->getProperty('class_key','modDocument');
$obj = $this->modx->newObject($classKey);
$classKey = $obj ? $obj->get('class_key') : $classKey;
$map = [
'modWebLink' => 'new_weblink',
'modSymLink' => 'new_symlink',
'modStaticResource' => 'new_static_resource',
];
if (array_key_exists($classKey, $map)) {
$permissions[] = $map[$classKey];
}
foreach ($permissions as $permission) {
if (!$this->modx->hasPermission($permission)) {
// allow the error message shown to the user (in modProcessor->run()) to show the right failed permission
$this->permission = $permission;
return false;
}
}
return true;
}
/**
* Create the modResource object for manipulation
* @return string|modResource
*/
public function initialize() {
/* get the class_key to determine classKey and resourceDir */
$classKey = $this->getProperty('class_key','modDocument');
$this->classKey = !empty($classKey) ? $classKey : 'modDocument';
$initialized = parent::initialize();
if (!$initialized) return $this->modx->lexicon('resource_err_create');
if (!$this->object instanceof $this->classKey) return $this->modx->lexicon('resource_err_class',array('class' => $this->classKey));
return $initialized;
}
/**
* Process the form and create the Resource
*
* {@inheritDoc}
*
* @return array|string
*/
public function beforeSet() {
/* default settings */
$this->prepareParent();
if ($this->getProperty('parent') === (int) $this->modx->getOption('tree_root_id') && !$this->modx->hasPermission('new_document_in_root')) {
return $this->modx->lexicon('permission_denied');
}
$set = $this->checkParentPermissions();
if ($set !== true) return $set;
if ($this->classKey === 'modSymLink') {
$this->checkSymLinkTarget();
}
if ($this->classKey === 'modWebLink') {
$this->checkWebLinkTarget();
}
$this->getWorkingContext();
if (!$this->workingContext) {
return $this->modx->lexicon('access_denied');
}
$set = $this->setFieldDefaults();
if ($set !== true) return $set;
$this->preparePageTitle();
$this->prepareAlias();
$this->handleResourceProperties();
$this->object->set('template',$this->getProperty('template',0));
$templateVariables = $this->addTemplateVariables();
if (!empty($templateVariables)) {
$this->object->addMany($templateVariables);
}
return parent::beforeSet();
}
/**
* Handle any properties-specific fields
*/
public function handleResourceProperties() {
if ($this->object->get('class_key') == 'modWebLink') {
$responseCode = $this->getProperty('responseCode');
if (!empty($responseCode)) {
$this->object->setProperty('responseCode',$responseCode);
}
}
}
/**
* {@inheritDoc}
* @return mixed
*/
public function beforeSave() {
if (!$this->object->get('class_key')) {
$this->object->set('class_key',$this->classKey);
}
$this->setMenuIndex();
$reloaded = (boolean)$this->getProperty('reloaded',false);
if ($reloaded && !$this->hasErrors() && !$this->checkForAllowableCreateToken()) {
return $this->modx->lexicon('resource_err_save');
}
return parent::beforeSave();
}
/**
* {@inheritDoc}
* @return mixed
*/
public function afterSave() {
$this->object->addLock();
$this->setParentToContainer();
$this->saveResourceGroups();
$this->checkIfSiteStart();
return parent::afterSave();
}
/**
* {@inheritDoc}
* @return mixed
*/
public function cleanup() {
$this->object->removeLock();
$this->clearCache();
return $this->success('', array('id' => $this->object->get('id')));
}
/**
* Set defaults for the fields if values are not passed
* @return mixed
*/
public function setFieldDefaults() {
$scriptProperties = $this->getProperties();
$scriptProperties['template'] = !isset($scriptProperties['template']) ? (integer) $this->workingContext->getOption('default_template', 0) : (integer) $scriptProperties['template'];
$scriptProperties['hidemenu'] = !isset($scriptProperties['hidemenu']) ? (integer) $this->workingContext->getOption('hidemenu_default', 0) : (empty($scriptProperties['hidemenu']) ? 0 : 1);
$scriptProperties['isfolder'] = empty($scriptProperties['isfolder']) ? 0 : 1;
$scriptProperties['richtext'] = !isset($scriptProperties['richtext']) ? (integer) $this->workingContext->getOption('richtext_default', 1) : (empty($scriptProperties['richtext']) ? 0 : 1);
$scriptProperties['donthit'] = empty($scriptProperties['donthit']) ? 0 : 1;
$scriptProperties['published'] = !isset($scriptProperties['published']) ? (integer) $this->workingContext->getOption('publish_default', 0) : (empty($scriptProperties['published']) ? 0 : 1);
$scriptProperties['cacheable'] = !isset($scriptProperties['cacheable']) ? (integer) $this->workingContext->getOption('cache_default', 1) : (empty($scriptProperties['cacheable']) ? 0 : 1);
$scriptProperties['searchable'] = !isset($scriptProperties['searchable']) ? (integer) $this->workingContext->getOption('search_default', 1) : (empty($scriptProperties['searchable']) ? 0 : 1);
$scriptProperties['content_type'] = !isset($scriptProperties['content_type']) ? (integer) $this->workingContext->getOption('default_content_type',1) : (integer)$scriptProperties['content_type'];
$scriptProperties['syncsite'] = empty($scriptProperties['syncsite']) ? 0 : 1;
$scriptProperties['menuindex'] = empty($scriptProperties['menuindex']) ? 0 : $scriptProperties['menuindex'];
$scriptProperties['deleted'] = empty($scriptProperties['deleted']) ? 0 : 1;
$scriptProperties['uri_override'] = empty($scriptProperties['uri_override']) ? 0 : 1;
if(empty($scriptProperties['createdon'])){
$scriptProperties['createdon'] = strftime('%Y-%m-%d %H:%M:%S');
}
if(empty($scriptProperties['createdby'])){
$scriptProperties['createdby'] = $this->modx->user->get('id');
}
/* publish and unpublish dates */
$now = time();
if (isset($scriptProperties['pub_date'])) {
if (empty($scriptProperties['pub_date'])) {
$scriptProperties['pub_date'] = 0;
} else {
$scriptProperties['pub_date'] = strtotime($scriptProperties['pub_date']);
if ($scriptProperties['pub_date'] <= $now) $scriptProperties['published'] = 1;
if ($scriptProperties['pub_date'] > $now) $scriptProperties['published'] = 0;
}
}
if (isset($scriptProperties['unpub_date'])) {
if (empty($scriptProperties['unpub_date'])) {
$scriptProperties['unpub_date'] = 0;
} else {
$scriptProperties['unpub_date'] = strtotime($scriptProperties['unpub_date']);
if ($scriptProperties['unpub_date'] < $now) $scriptProperties['published'] = 0;
}
}
/* modDocument content is posted as ta */
if (isset($scriptProperties['ta'])) $scriptProperties['content'] = $scriptProperties['ta'];
/* deny publishing if not permitted */
if (!$this->modx->hasPermission('publish_document')) {
$scriptProperties['publishedon'] = 0;
$scriptProperties['publishedby'] = 0;
$scriptProperties['pub_date'] = 0;
$scriptProperties['unpub_date'] = 0;
$scriptProperties['published'] = 0;
}
if (isset($scriptProperties['published'])) {
if (empty($scriptProperties['publishedon'])) {
$scriptProperties['publishedon'] = $scriptProperties['published'] ? time() : 0;
} else {
$scriptProperties['publishedon'] = strtotime($scriptProperties['publishedon']);
}
$scriptProperties['publishedby'] = $scriptProperties['published'] ? !empty($scriptProperties['publishedby']) ? $scriptProperties['publishedby'] : $this->modx->user->get('id') : 0;
}
$this->setProperties($scriptProperties);
return true;
}
/**
* Handle if parent is a context
*/
public function checkIfParentIsContext() {
$parent = $this->getProperty('parent');
if (!empty($parent) && !is_numeric($parent)) {
$ctxCnt = $this->modx->getCount('modContext',array('key' => $parent));
if ($ctxCnt > 0) {
$this->setProperty('context_key',$parent);
}
$this->setProperty('parent',0);
}
}
/**
* Set and prepare the parent field
* @return int
*/
public function prepareParent() {
$this->checkIfParentIsContext();
$parent = $this->getProperty('parent');
$parent = empty($parent) ? 0 : intval($parent);
$this->setProperty('parent',$parent);
return $parent;
}
/**
* Make sure parent exists and user can add_children to the parent
* @return boolean|string
*/
public function checkParentPermissions() {
$parent = null;
$parentId = intval($this->getProperty('parent'));
if ($parentId > 0) {
$this->parentResource = $this->modx->getObject('modResource',$parentId);
if ($this->parentResource) {
if (!$this->parentResource->checkPolicy('add_children')) {
return $this->modx->lexicon('resource_add_children_access_denied');
}
} else {
return $this->modx->lexicon('resource_err_nfs', array('id' => $parentId));
}
} elseif (!$this->modx->hasPermission('new_document_in_root')) {
return $this->modx->lexicon('resource_add_children_access_denied');
}
return true;
}
/**
* Prepare the pagetitle for insertion
* @return string
*/
public function preparePageTitle() {
$pageTitle = $this->getProperty('pagetitle','');
if (!empty($pageTitle)) {
$pageTitle = trim($pageTitle);
}
/* default pagetitle if not reloading template */
if (!$this->getProperty('reloadOnly',false)) {
if ($pageTitle === '') {
$pageTitle = $this->modx->lexicon('resource_untitled');
}
}
$this->setProperty('pagetitle',$pageTitle);
return $pageTitle;
}
/**
* Get the working Context for the Resource
*
* @return modContext
*/
public function getWorkingContext() {
$contextKey = $this->getProperty('context_key','');
if (empty($contextKey)) {
$contextKey = $this->parentResource ? $this->parentResource->get('context_key') : 'web';
$this->setProperty('context_key',$contextKey);
}
$this->workingContext = $this->modx->getContext($contextKey);
return $this->workingContext;
}
/**
* Clean and prepare the alias, automatically generating it if the option is set
* @return string
*/
public function prepareAlias() {
// The user submitted alias & page title
$alias = $this->getProperty('alias');
$pageTitle = $this->getProperty('pagetitle');
$autoGenerated = false;
// If we don't have an alias passed, and automatic_alias is enabled, we generate one from the pagetitle.
if (empty($alias) && $this->workingContext->getOption('automatic_alias', false)) {
$alias = $this->object->cleanAlias($pageTitle);
$autoGenerated = true;
}
$friendlyUrlsEnabled = $this->workingContext->getOption('friendly_urls', false) && (!$this->getProperty('reloadOnly',false) || !empty($pageTitle));
// Check for duplicates
$duplicateContext = $this->workingContext->getOption('global_duplicate_uri_check', false) ? '' : $this->getProperty('context_key');
$aliasPath = $this->object->getAliasPath($alias,$this->getProperties());
$duplicateId = $this->object->isDuplicateAlias($aliasPath, $duplicateContext);
// We have a duplicate!
if ($duplicateId) {
// If friendly urls is enabled, we throw an error about the alias
if ($friendlyUrlsEnabled) {
$err = $this->modx->lexicon('duplicate_uri_found', array(
'id' => $duplicateId,
'uri' => $aliasPath,
));
$this->addFieldError('uri', $err);
if ($this->getProperty('uri_override',0) !== 1) {
$this->addFieldError('alias', $err);
}
}
// If friendly urls is not enabled, and we automatically generated the alias, then we just unset it
elseif ($autoGenerated) {
$alias = '';
}
}
// If the alias is empty yet friendly urls is enabled, add an error to the alias field
if (empty($alias) && $friendlyUrlsEnabled) {
$this->addFieldError('alias', $this->modx->lexicon('field_required'));
}
// Set the new alias and return it, too.
$this->setProperty('alias',$alias);
return $alias;
}
/**
* Check if new resource token posted from manager
* @return boolean
*/
public function checkForAllowableCreateToken() {
$tokenPassed = true;
$token = $this->getProperty('create-resource-token',false);
if (!empty($token)) {
$tokenPassed = false;
if (!is_null($token)) {
if (isset($_SESSION['newResourceTokens']) && !is_null($_SESSION['newResourceTokens'])) {
$search = array_search($token, $_SESSION['newResourceTokens']);
if ($search !== false) {
unset($_SESSION['newResourceTokens'][$search]);
$tokenPassed = true;
}
}
}
}
return $tokenPassed;
}
/**
* Add Template Variables to the Resource object
* @return array
*/
public function addTemplateVariables() {
$addedTemplateVariables = array();
$templateKey = $this->getProperty('template',0);
$this->template = $this->modx->getObject('modTemplate',$templateKey);
if (!empty($templateKey) && $this->template) {
$templateVars = $this->object->getTemplateVars();
/** @var modTemplateVar $tv */
foreach ($templateVars as $tv) {
$tvKey = 'tv'.$tv->get('id');
$value = $this->getProperty($tvKey,$tv->get('default_text'));
switch ($tv->get('type')) {
/* ensure tag types trim whitespace from tags */
case 'tag':
case 'autotag':
$tags = explode(',',$value);
$newTags = array();
foreach ($tags as $tag) {
$newTags[] = trim($tag);
}
$value = implode(',',$newTags);
break;
case 'date':
$value = empty($value) ? '' : strftime('%Y-%m-%d %H:%M:%S',strtotime($value));
break;
case 'url':
if ($this->getProperty($tvKey. '_prefix','--') != '--') {
$value = str_replace(array('ftp://','http://'),'', $value);
$value = $this->getProperty($tvKey.'_prefix','').$value;
}
break;
default:
/* handles checkboxes & multiple selects elements */
if (is_array($value)) {
$featureInsert = array();
foreach ($value as $featureValue => $featureItem) {
$featureInsert[count($featureInsert)] = $featureItem;
}
$value = implode('||',$featureInsert);
}
break;
}
/* save value if it was modified */
$default = $tv->processBindings($tv->get('default_text'),0);
if (strcmp($value,$default) != 0) {
/** @var modTemplateVarResource $templateVarResource */
$templateVarResource = $this->modx->newObject('modTemplateVarResource');
$templateVarResource->set('tmplvarid',$tv->get('id'));
$templateVarResource->set('value',$value);
$addedTemplateVariables[] = $templateVarResource;
}
}
}
return $addedTemplateVariables;
}
/**
* Set the menu index on the Resource, incrementing if not set
* @return int
*/
public function setMenuIndex() {
$autoMenuIndex = $this->workingContext->getOption('auto_menuindex', true);
$menuIndex = $this->getProperty('menuindex',0);
if (!empty($autoMenuIndex) && empty($menuIndex)) {
$menuIndex = $this->modx->getCount('modResource',array(
'parent' => $this->object->get('parent'),
'context_key' => $this->object->get('context_key'),
));
}
$this->object->set('menuindex',$menuIndex);
return $menuIndex;
}
/**
* Invoke OnBeforeDocFormSave event, and allow non-empty responses to prevent save
* @return boolean
*/
public function fireBeforeSaveEvent() {
/** @var array $OnBeforeDocFormSave */
$OnBeforeDocFormSave = $this->modx->invokeEvent('OnBeforeDocFormSave',array(
'mode' => modSystemEvent::MODE_NEW,
'id' => 0,
'resource' => &$this->object,
'reloadOnly' => $this->getProperty('reloadOnly',false),
));
if (is_array($OnBeforeDocFormSave)) {
$canSave = false;
foreach ($OnBeforeDocFormSave as $msg) {
if (!empty($msg)) {
$canSave .= $msg."\n";
}
}
} else {
$canSave = $OnBeforeDocFormSave;
}
return $canSave;
}
/**
* Save the Resource Groups on the object
*
* @return void
*/
public function saveResourceGroups() {
$resourceGroups = $this->getProperty('resource_groups',null);
if ($resourceGroups !== null) {
$resourceGroups = $this->modx->fromJSON($resourceGroups);
if (is_array($resourceGroups)) {
foreach ($resourceGroups as $id => $resourceGroupAccess) {
/* prevent adding records for non-existing groups */
$resourceGroup = $this->modx->getObject('modResourceGroup',$resourceGroupAccess['id']);
if (empty($resourceGroup)) continue;
/* if assigning to group */
if (!empty($resourceGroupAccess['access'])) {
/** @var modResourceGroupResource $resourceGroupResource */
$resourceGroupResource = $this->modx->getObject('modResourceGroupResource',array(
'document_group' => $resourceGroupAccess['id'],
'document' => $this->object->get('id'),
));
if (empty($resourceGroupResource)) {
$resourceGroupResource = $this->modx->newObject('modResourceGroupResource');
}
$resourceGroupResource->set('document_group',$resourceGroupAccess['id']);
$resourceGroupResource->set('document',$this->object->get('id'));
if ($resourceGroupResource->save()) {
$this->modx->invokeEvent('OnResourceAddToResourceGroup',array(
'mode' => 'resource-create',
'resource' => &$this->object,
'resourceGroup' => &$resourceGroup,
));
}
/* if removing access to group */
} else {
$resourceGroupResource = $this->modx->getObject('modResourceGroupResource',array(
'document_group' => $resourceGroupAccess['id'],
'document' => $this->object->get('id'),
));
if ($resourceGroupResource instanceof modResourceGroupResource) {
if ($resourceGroupResource->remove()) {
$this->modx->invokeEvent('OnResourceRemoveFromResourceGroup',array(
'mode' => 'resource-create',
'resource' => &$this->object,
'resourceGroup' => &$resourceGroup,
));
}
}
}
} /* end foreach */
} /* end if is_array */
}
}
/**
* Invoke OnDocFormSave event
* @return void
*/
public function fireAfterSaveEvent() {
$this->modx->invokeEvent('OnDocFormSave', array(
'mode' => modSystemEvent::MODE_NEW,
'id' => $this->object->get('id'),
'resource' => &$this->object,
'reloadOnly' => $this->getProperty('reloadOnly',false),
));
}
/**
* Update parent to be a container if user has save permission
*
* @return boolean
*/
public function setParentToContainer() {
$saved = false;
$autoIsFolder = $this->modx->getOption('auto_isfolder', null, true);
if ($autoIsFolder && $this->parentResource && $this->parentResource instanceof modResource && $this->parentResource->checkPolicy('save')) {
$this->parentResource->set('isfolder', true);
$saved = $this->parentResource->save();
}
return $saved;
}
/**
* Quick check to make sure it's not site_start, if so, publish if not published to prevent site error
*
* @return boolean
*/
public function checkIfSiteStart() {
$saved = false;
if ($this->object->get('id') == $this->workingContext->getOption('site_start') && !$this->object->get('published')) {
$this->object->set('published',true);
$saved = $this->object->save();
}
return $saved;
}
/**
* Check that the symlink target is a valid resource ID
* @return bool
*/
public function checkSymLinkTarget() {
$target = $this->getProperty('content', null);
$matches = array();
$countFoundTags = $this->modx->getParser()->collectElementTags($target, $matches);
if ($target === null || $target === '' || $countFoundTags) {
return true;
}
if (filter_var($target, FILTER_VALIDATE_INT) === false) {
$this->addFieldError('modx-symlink-content', $this->modx->lexicon('resource_err_symlink_target_invalid'));
return false;
}
$targetResource = $this->modx->getObject('modResource', $target);
if (!$targetResource) {
$this->addFieldError('modx-symlink-content', $this->modx->lexicon('resource_err_symlink_target_nf'));
return false;
}
return true;
}
/**
* Check that the weblink target is a valid resource ID if it contains an integer value
* @return bool
*/
public function checkWebLinkTarget() {
$target = $this->getProperty('content', null);
$matches = array();
$countFoundTags = $this->modx->getParser()->collectElementTags($target, $matches);
if ($target === null || $target === '' || $countFoundTags) {
return true;
}
if (filter_var($target, FILTER_VALIDATE_INT) !== false) {
$targetResource = $this->modx->getObject('modResource', $target);
if (!$targetResource) {
$this->addFieldError('modx-weblink-content', $this->modx->lexicon('resource_err_weblink_target_nf'));
return false;
}
}
return true;
}
/**
* Clear the cache if specified
* @return boolean
*/
public function clearCache() {
$clear = $this->getProperty('syncsite',$this->modx->getOption('syncsite_default')) || $this->getProperty('clearCache',false);
if ($clear) {
$this->modx->cacheManager->refresh(array(
'db' => array(),
'auto_publish' => array('contexts' => array($this->workingContext->get('key'))),
'context_settings' => array('contexts' => array($this->workingContext->get('key'))),
'resource' => array('contexts' => array($this->workingContext->get('key'))),
));
}
return $clear;
}
}
return 'modResourceCreateProcessor';