public function save($args) { $db = Database::connection(); $db->begin(); // simple argument checking if (!isset($args['page'])) { throw new Wikidot_Facade_Exception_WrongArguments("Page argument must be passed"); } $pm = new WDPermissionManager(); $now = new ODate(); // page (existant or not) name $arg_page = WDStringUtils::toUnixName($args['page']); // parse the rest (beside page name) unset($args['page']); $this->parseArgs($args, array("performer", "site")); try { // parse page name to figure out if it points to an existant page $page = $this->_parsePage($this->site, $arg_page); $new = false; // check permissions to edit the page $pm->hasPagePermission('edit', $this->performer, $page->getCategory(), $page); } catch (Wikidot_Facade_Exception_WrongArguments $e) { if ($this->source === null) { $this->source = ""; } if ($this->title === null) { $this->title = $arg_page; } $new = true; $category_name = preg_replace('/^([^:]*):.*$/', '\\1', $arg_page); if ($category_name == $arg_page) { $category_name = '_default'; } $category = $this->_getOrCreateCategory($this->site, $category_name); $page = new DB_Page(); $page->setSiteId($this->site->getSiteId()); $page->setCategoryId($category->getCategoryId()); $page->setUnixName($arg_page); $page->setDateCreated(new ODate()); $page->setOwnerUserId($this->performer->getUserId()); $page->save(); $compiled = new DB_PageCompiled(); $compiled->setPageId($page->getPageId()); $compiled->save(); } // get current revision and metadata if (!$new) { $cur_rev = $page->getCurrentRevision(); $cur_meta = $cur_rev->getMetadata(); } // construct new metadata if ($new) { $new_meta = new DB_PageMetadata(); $new_meta->setUnixName($arg_page); $new_meta->setOwnerUserId($this->performer->getUserId()); } else { $new_meta = clone $cur_meta; $new_meta->setNew(true); $new_meta->setMetadataId(null); } // construct new revision $new_rev = new DB_PageRevision(); $new_rev->setSiteId($this->site->getSiteId()); $new_rev->setPageId($page->getPageId()); $new_rev->setUserId($this->performer->getUserId()); $new_rev->setDateLastEdited($now); if ($new) { $new_rev->setRevisionNumber(0); } else { $new_rev->setRevisionNumber($cur_rev->getRevisionNumber() + 1); } $src_changed = false; $title_changed = false; $parent_changed = false; $tags_changed = false; // handle source change if ($new || $this->source !== null && $page->getSource() != $this->source) { $new_src = new DB_PageSource(); $new_src->setText($this->source); $new_src->save(); $new_rev->setSourceId($new_src->getSourceId()); $src_changed = true; } else { $new_rev->setSourceId($cur_rev->getSourceId()); $new_rev->setSinceFullSource($cur_rev->getSinceFullSource()); $new_rev->setDiffSource($cur_rev->getDiffSource()); } // handle tags change if ($this->tags) { $new_tags = $this->tags; $cur_tags = $page->getTagsAsArray(); sort($cur_tags); sort($new_tags); if ($cur_tags != $new_tags) { $tags_changed = true; $tags_deleted = array(); $tags_added = array(); foreach ($cur_tags as $tag) { if (!in_array($tag, $new_tags)) { $c = new Criteria(); $c->add('page_id', $page->getPageId()); $c->add('tag', $tag); if ($t = DB_PageTagPeer::instance()->selectOne($c)) { $t->delete(); $tags_deleted[] = $tag; } } } foreach ($new_tags as $tag) { if (!in_array($tag, $cur_tags)) { $t = new DB_PageTag(); $t->getPageId($page->getPageId()); $t->setSiteId($this->site->getSiteId()); $t->setTag($tag); $t->save(); $tags_added[] = $tag; } } } } // handle metadata: title change if ($new || $this->title !== null && $cur_meta->getTitle() != $this->title) { $new_meta->setTitle($this->title); $page->setTitle($this->title); $title_changed = true; } // handle metadata: parent page change if ($this->parent_page) { if (!$cur_meta->getParentPageId() || $cur_meta->getParentPageId() != $this->parent_page->getPageId()) { $new_meta->setParentPageId($this->parent_page->getPageId()); $parent_changed = true; } } if ($this->clear_parent_page && $page->getParentPageId()) { $new_meta->setParentPageId(null); $parent_changed = true; } $meta_changed = $title_changed || $parent_changed; // decide whether to use previous metadata or create a new object if ($meta_changed) { $new_meta->save(); $new_rev->setMetadataId($new_meta->getMetadataId()); } else { $new_rev->setMetadataId($cur_meta->getMetadataId()); } // set flag on revision if ($new) { $new_rev->setFlagNew(true); } else { if ($src_changed) { $new_rev->setFlagText(true); } if ($title_changed) { $new_rev->setFlagTitle(true); } if ($parent_changed) { $new_rev->setFlagMeta(true); } } if ($src_changed || $meta_changed || $tags_changed) { $new_rev->save(); $page->setSourceId($new_rev->getSourceId()); $page->setDateLastEdited($now); $page->setMetadataId($new_rev->getMetadataId()); $page->setRevisionNumber($new_rev->getRevisionNumber()); $page->setRevisionId($new_rev->getRevisionId()); $page->save(); $db->commit(); $GLOBALS['site'] = $this->site; $outdater = new Outdater(); if ($src_changed) { $outdater->pageEvent("source_changed", $page); } if ($title_changed) { $outdater->pageEvent("title_changed", $page); } if ($parent_changed) { $outdater->pageEvent("parent_changed", $page); } if ($tags_changed) { $outdater->pageEvent("tag_changed", $page); } } else { /* This place is reached when API client tries to set source or * title or parent page or tags that are already set (in the DB) * to the same value. * * Let's suppose doing nothing is the desired behavior in this case * * Other possible way to react can be raising an exception. * But it should be different from Wikidot_Facade_Exception_WrongArguments * because this one implies client error (and client does not need * to know the exact database state). */ } }
public function savePageEvent($runData) { $pl = $runData->getParameterList(); $pageId = $pl->getParameterValue("page_id"); $mode = $pl->getParameterValue("mode"); if ($pl->getParameterValue("form")) { $data = array(); $newpages = array(); foreach ($runData->getParameterList()->asArray() as $name => $val) { $m = array(); if (preg_match("/^field_(.*)\$/", $name, $m)) { $data[$m[1]] = $val; } } $source = Wikidot_Yaml::dump($data); } else { $source = trim($pl->getParameterValue("source")); } $comments = trim($pl->getParameterValue("comments")); $title = trim($pl->getParameterValue("title")); $userId = $runData->getUserId(); if ($userId == null) { $userString = $runData->createIpString(); } if ($title === '') { $title = null; } $unixName = $pl->getParameterValue("wiki_page"); $unixName = WDStringUtils::toUnixName($unixName); // purify! (for sure) $lockId = $pl->getParameterValue("lock_id"); $lockSecret = $pl->getParameterValue("lock_secret"); $site = $runData->getTemp("site"); // validate input first $db = Database::connection(); $db->begin(); // remove old locks. if (strlen8($title) > 128) { throw new ProcessException(_("Title of the page should not be longer than 128 characters."), "title_too_long"); } // if page source not too long... if (strlen8($source) > 200000) { throw new ProcessException(_("Source of the page should not be longer than 200 000 characters which is large enough. Pages longer than that can indicate improper usage \tof the wiki site."), "source_too_long"); } // if comment too long if (strlen8($comments) > 210) { throw new ProcessException(_("The changes comment is longer than 200 characters. Please keep this description short and informative. And no longer than this limit please..."), "comment_too_long"); } $autoincrement = false; $nowDate = new ODate(); if ($pageId === null || $pageId === '') { if (preg_match(';^([a-z0-9]+:)?' . self::$AUTOINCREMENT_PAGE . '$;', $unixName)) { $autoincrement = true; } if (!$autoincrement) { DB_PageEditLockPeer::instance()->deleteOutdatedByPageName($site->getSiteId(), $unixName); } // a page should be created! // extract category name if (strpos($unixName, ':') != false) { // ok, there is category! $exp = explode(':', $unixName); $categoryName = $exp[0]; } else { // no category name, "_default" assumed $categoryName = "_default"; } // check if category exists. if not - create it! $category = DB_CategoryPeer::instance()->selectByName($categoryName, $site->getSiteId(), false); if ($category == null) { // create the category - just clone the default category!!! $category = DB_CategoryPeer::instance()->selectByName("_default", $site->getSiteId(), false); $category->setName($categoryName); // fill with some important things - we assume the _default category exists!!! IT REALLY SHOULD!!! $category->setCategoryId(null); $category->setNew(true); // this will make it INSERT, not UPDATE on save() $category->setPerPageDiscussion(null); //default value // set default permissions theme and license $category->setPermissionsDefault(true); $category->setThemeDefault(true); $category->setLicenseDefault(true); $category->setNavDefault(true); $category->save(); } // first look at permissions! WDPermissionManager::instance()->hasPagePermission('create', $runData->getUser(), $category); // check the locks! // check if the lock still exists. if (!$autoincrement) { $c = new Criteria(); $c->add("lock_id", $lockId); $c->add("secret", $lockSecret); $lock = DB_PageEditLockPeer::instance()->selectOne($c); if ($lock == null) { $page = DB_PagePeer::instance()->selectByName($site->getSiteId(), $unixName); if ($page != null) { // page exists!!! error! $runData->ajaxResponseAdd("noLockError", "other_locks"); $runData->ajaxResponseAdd("pageExists", true); $runData->ajaxResponseAdd("locked", true); //well, it is somehow locked... $runData->setModuleTemplate("edit/NewPageExistsWinModule"); $runData->contextAdd("nonrecoverable", true); $runData->ajaxResponseAdd("nonrecoverable", true); $db->commit(); return; } // check if we can TRANSPARENTLY recreate the lock IF there is no // conflicting lock and the revision_id has not changed. $lock = new DB_PageEditLock(); $lock->setPageUnixName($unixName); $lock->setSiteId($site->getSiteId()); $lock->setUserId($runData->getUserId()); $lock->setUserString($runData->getSession()->getIpAddress()); $lock->setDateStarted(new ODate()); $lock->setDateLastAccessed(new ODate()); $lock->setMode("page"); $conflictLocks = $lock->getConflicts(); if ($conflictLocks == null) { // safely recreate lock $secret = md5(time() . rand(1000, 9999)); $lock->setSecret($secret); $lock->setSessionId($runData->getSession()->getSessionId()); $lock->save(); $lockId = $lock->getLockId(); // send back new lock information $runData->ajaxResponseAdd("lockRecreated", true); $runData->ajaxResponseAdd("lockId", $lockId); $runData->ajaxResponseAdd("lockSecret", $secret); $runData->ajaxResponseAdd('timeLeft', 60 * 15); } else { $runData->ajaxResponseAdd("noLockError", "other_locks"); $runData->setModuleTemplate("edit/LockInterceptedWinModule"); $runData->contextAdd("locks", $conflictLocks); $db->commit(); return; } } else { $lock->setDateLastAccessed(new ODate()); $lock->save(); $runData->ajaxResponseAdd('timeLeft', 60 * 15); } } /* Change unixName to integer. */ if ($autoincrement) { /* Check max number taken. */ $db = Database::connection(); $q = "select max(substring(unix_name from '[0-9]+')::integer) + 1 as max from page where category_id={$category->getCategoryId()} AND unix_name ~ '^([a-z0-9]+:)?[0-9]+\$'"; $r = $db->query($q); $row = $r->nextRow(); $unixName = $row['max']; if ($category->getName() != '_default') { $unixName = $category->getName() . ':' . $unixName; } $runData->ajaxResponseAdd('pageUnixName', $unixName); } $page = new DB_Page(); $page->obtainPK(); $pageRevision = new DB_PageRevision(); $pageRevision->setSiteId($site->getSiteId()); $pageRevision->setPageId($page->getPageId()); $pageRevision->setFlagNew(true); $pageRevision->setComments($comments); $pageRevision->obtainPK(); $pageRevision->setDateLastEdited($nowDate); $pageRevision->setPageId($page->getPageId()); $page->setRevisionId($pageRevision->getRevisionId()); $pageSource = new DB_PageSource(); $pageSource->setText($source); $pageSource->save(); $pageRevision->setSourceId($pageSource->getSourceId()); $pageMetadata = new DB_PageMetadata(); $pageMetadata->setTitle($title); $pageMetadata->setUnixName($unixName); if ($userId) { $pageMetadata->setOwnerUserId($userId); } $pageMetadata->save(); $pageRevision->setMetadataId($pageMetadata->getMetadataId()); $pageCompiled = new DB_PageCompiled(); $pageCompiled->setPageId($page->getPageId()); $newPage = true; // update the page object $page->setUnixName($unixName); $page->setDateCreated($nowDate); $page->setSiteId($site->getSiteId()); $page->setSourceId($pageSource->getSourceId()); $page->setMetadataId($pageMetadata->getMetadataId()); $page->setTitle($title); $page->setDateLastEdited($nowDate); $pageCompiled = new DB_PageCompiled(); $pageCompiled->setPageId($page->getPageId()); $pageCompiled->outdate(); $newPage = true; $page->setCategoryId($category->getCategoryId()); // now set user_id, user_string if ($userId) { $pageRevision->setUserId($userId); $page->setLastEditUserId($userId); } else { $pageRevision->setUserId(0); $page->setLastEditUserId(0); $pageRevision->setUserString($userString); $page->setLastEditUserString($userString); } $page->setOwnerUserId($userId); $pageRevision->save(); $page->setRevisionId($pageRevision->getRevisionId()); $page->save(); $pageCompiled->save(); $sourceChanged = true; $outdater = new Outdater(); $outdater->pageEvent("new_page", $page); // index page if (!$autoincrement) { $c = new Criteria(); $c->add("lock_id", $lockId); DB_PageEditLockPeer::instance()->delete($c); } EventLogger::instance()->logNewPage($page); } else { // THE PAGE ALREADY EXISTS DB_PageEditLockPeer::instance()->deleteOutdated($pageId); $c = new Criteria(); $c->add("page_id", $pageId); $c->setForUpdate(true); $page = DB_PagePeer::instance()->selectOne($c); if ($page == null) { throw new ProcessException(_("Page does not exist.")); } // check permissions $category = $page->getCategory(); WDPermissionManager::instance()->hasPagePermission('edit', $runData->getUser(), $category, $page); // check if the lock still exists. $c = new Criteria(); $c->add("lock_id", $lockId); $c->add("secret", $lockSecret); $lock = DB_PageEditLockPeer::instance()->selectOne($c); if ($lock == null) { OzoneLogger::instance()->debug("no lock"); // no lock!!! not good. if ($page->getRevisionId() != $pl->getParameterValue("revision_id")) { // this is nonrecoverable. // author should stop editing now!!! OzoneLogger::instance()->debug("page changed"); $runData->ajaxResponseAdd("noLockError", "page_changed"); $runData->setModuleTemplate("edit/LockInterceptedWinModule"); $runData->contextAdd("nonrecoverable", true); $runData->ajaxResponseAdd("nonrecoverable", true); $db->commit(); return; } // check if we can TRANSPARENTLY recreate the lock IF there is no // conflicting lock and the revision_id has not changed. $lock = new DB_PageEditLock(); $lock->setPageId($page->getPageId()); $lock->setPageUnixName($page->getUnixName()); $lock->setSiteId($site->getSiteId()); $lock->setUserId($runData->getUserId()); $lock->setUserString($runData->getSession()->getIpAddress()); $lock->setDateStarted(new ODate()); $lock->setDateLastAccessed(new ODate()); $lock->setMode($mode); if ($mode == "section") { $rangeStart = $pl->getParameterValue("range_start"); $rangeEnd = $pl->getParameterValue("range_end"); $lock->setRangeStart($rangeStart); $lock->setRangeEnd($rangeEnd); } $conflictLocks = $lock->getConflicts(); if ($conflictLocks == null) { // safely recreate lock $secret = md5(time() . rand(1000, 9999)); $lock->setSecret($secret); $lock->setSessionId($runData->getSession()->getSessionId()); $lock->save(); $lockId = $lock->getLockId(); // send back new lock information $runData->ajaxResponseAdd("lockRecreated", true); $runData->ajaxResponseAdd("lockId", $lockId); $runData->ajaxResponseAdd("lockSecret", $secret); $runData->ajaxResponseAdd('timeLeft', 60 * 15); } else { $runData->ajaxResponseAdd("noLockError", "other_locks"); $runData->setModuleTemplate("edit/LockInterceptedWinModule"); $runData->contextAdd("locks", $conflictLocks); $db->commit(); return; } } else { $lock->setDateLastAccessed(new ODate()); $lock->save(); $runData->ajaxResponseAdd('timeLeft', 60 * 15); // here is a good place to check conditions for // "save & continue" which when first called // creates new revision, but the subsequent calls // do not. } // check if source or metadata has changed. if neither is changed - do nothing // get current revision $currentRevision = $page->getCurrentRevision(); // compare source text $oldSourceText = $page->getSource(); $sourceChanged = false; if ($mode == "append") { $source = $oldSourceText . "\n\n" . $source; } if ($mode == "section") { $rangeStart = $lock->getRangeStart(); //$pl->getParameterValue("range_start"); $rangeEnd = $lock->getRangeEnd(); //$pl->getParameterValue("range_end"); $s2 = explode("\n", $oldSourceText); // fix source last empty line if (!ereg("\n\$", $source)) { $source .= "\n"; } array_splice($s2, $rangeStart, $rangeEnd - $rangeStart + 1, explode("\n", $source)); $source = implode("\n", $s2); } if ($oldSourceText !== $source) { $sourceChanged = true; } // create new revision $pageRevision = new DB_PageRevision(); $pageRevision->setSiteId($site->getSiteId()); // compare metadata $metadataChanged = false; $oldMetadata = $page->getMetadata(); // title if ($mode == 'page') { // check only if the whole page is edited if ($title !== $oldMetadata->getTitle()) { $pageRevision->setFlagTitle(true); $metadataChanged = true; } } // and act accordingly to the situation if ($sourceChanged == false && $metadataChanged == false) { $c = new Criteria(); $c->add("lock_id", $lockId); DB_PageEditLockPeer::instance()->delete($c); $db->commit(); return; } $pageRevision->setPageId($page->getPageId()); $pageRevision->setDateLastEdited($nowDate); $pageRevision->setRevisionNumber($currentRevision->getRevisionNumber() + 1); if ($sourceChanged) { $fullSource = false; // first check if store new source as a diff or as a full-source. if (true || $currentRevision->getSinceFullSource() > 9) { $fullSource = true; } else { // also compare size of diff against size of new source. // must be less than %50 to qualify $differ = new ODiff(); $diff = $differ->diffString($oldSourceText, $source); if (strlen($diff) > 0.5 * strlen($source)) { $fullSource = true; } } $pageSource = new DB_PageSource(); if ($fullSource) { $pageSource->setText($source); } else { $pageSource->setText($diff); $pageRevision->setDiffSource(true); $pageRevision->setSinceFullSource($currentRevision->getSinceFullSource() + 1); } $pageSource->save(); $pageRevision->setSourceId($pageSource->getSourceId()); $pageRevision->setFlagText(true); } else { // copy source id $pageRevision->setSourceId($currentRevision->getSourceId()); $pageRevision->setSinceFullSource($currentRevision->getSinceFullSource()); $pageRevision->setDiffSource($currentRevision->getDiffSource()); } if ($metadataChanged) { $pageMetadata = clone $oldMetadata; $pageMetadata->setNew(true); $pageMetadata->setMetadataId(null); $pageMetadata->setTitle($title); $pageMetadata->save(); $pageRevision->setMetadataId($pageMetadata->getMetadataId()); } else { // copy metadata id $pageRevision->setMetadataId($currentRevision->getMetadataId()); } // now set user_id, user_string if ($userId) { $pageRevision->setUserId($userId); $page->setLastEditUserId($userId); } else { $pageRevision->setUserId(0); $page->setLastEditUserId(0); $pageRevision->setUserString($userString); $page->setLastEditUserString($userString); } $pageRevision->setComments($comments); $pageRevision->save(); $page->setRevisionId($pageRevision->getRevisionId()); // update Page object $page->setSourceId($pageRevision->getSourceId()); if ($mode == 'page') { $page->setTitle($title); } $page->setDateLastEdited($nowDate); $page->setMetadataId($pageRevision->getMetadataId()); $page->setRevisionNumber($pageRevision->getRevisionNumber()); $page->save(); // also if "section edit" - find other locks that refer to // blocks with higher line numbers and change start/end accordingly if ($mode == "section") { $c = new Criteria(); $c->add("page_id", $pageId); $c->add("range_start", $lock->getRangeEnd(), ">="); $c->add("mode", "section"); $laterLocks = DB_PageEditLockPeer::instance()->select($c); if (count($laterLocks) > 0) { // take the length of the current lock $sectionLength = $lock->getRangeEnd() - $lock->getRangeStart() + 1; $newSourceLength = count(explode("\n", trim($pl->getParameterValue("source")))) + 1; // +1 for the new line at the end $lengthDifference = $newSourceLength - $sectionLength; foreach ($laterLocks as $llock) { $llock->setRangeStart($llock->getRangeStart() + $lengthDifference); $llock->setRangeEnd($llock->getRangeEnd() + $lengthDifference); $llock->save(); } } } // OUTDATING PARTY!!! $outdater = new Outdater(); if ($sourceChanged) { $outdater->pageEvent("source_changed", $page); } if ($metadataChanged) { $outdater->pageEvent("title_changed", $page); } // index page EventLogger::instance()->logSavePage($page); } // remove lock too? if (!$pl->getParameterValue("and_continue") && !$autoincrement) { $c = new Criteria(); $c->add("lock_id", $lockId); DB_PageEditLockPeer::instance()->delete($c); $runData->ajaxResponseAdd("revisionId", $pageRevision->getRevisionId()); } $db->commit(); }