getHistoryPermissionAndUser($page_id); $users = $this->getEditors($page_id, $user_id); return array( 'current_revision' => $this->getCurrentRevisionInfo(), 'editors' => $users, 'editors_warning' => $this->getEditorsWarning($users), 'revisions' => $history_permission ? $this->getPageRevisions($page_id) : array(), ); } /** * Returns current admin user id (even, when called from front-end) and it's revision history view permission * * @param int $page_id * @return Array */ protected function getHistoryPermissionAndUser($page_id) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $user_id = (int)$this->Application->RecallVar($this->Application->isAdmin ? 'user_id' : 'admin_user_id'); $history_permission = $perm_helper->CheckUserPermission($user_id, 'CATEGORY.REVISION.HISTORY.VIEW', 0, $page_id); return Array ($user_id, $history_permission); } /** * Returns information about given page editors. * * @param integer $page_id Page, that is being edited. * @param integer $user_id User, who is editing a page. * * @return array */ protected function getEditors($page_id, $user_id) { $where_clause = array( 'pr.PageId = ' . $page_id, 'pr.CreatedById <> ' . $user_id, 'pr.IsDraft = 1', ); $sql = 'SELECT CASE pr.CreatedById WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE IF(u.Username = "", u.Email, u.Username) END FROM ' . $this->Application->getUnitOption('page-revision', 'TableName') . ' pr LEFT JOIN ' . TABLE_PREFIX . 'Users u ON u.PortalUserId = pr.CreatedById WHERE (' . implode(') AND (', $where_clause) . ')'; return $this->Conn->GetCol($sql); } /** * Returns information about current revision. * * @return array */ protected function getCurrentRevisionInfo() { /** @var kDBItem $revision */ $revision = $this->Application->recallObject('page-revision.current'); $status_label = $this->getRevisionStatusText($revision); $draft = $revision->GetDBField('IsDraft'); $title = $this->getAdminPhrase($draft ? 'la_title_EditingDraft' : 'la_title_ViewingRevision'); $current_revision_info = array( 'title' => sprintf($title, $revision->GetDBField('RevisionNumber'), $status_label), 'status' => $revision->GetDBField('Status'), 'saved' => '', 'toolbar_state' => $this->getToolbarButtonsState($revision), ); $auto_save_time = $revision->GetDBField('AutoSavedOn'); if ( $auto_save_time ) { $phrase = $this->getAdminPhrase($draft ? 'la_DraftSavedAt' : 'la_SavedAt'); $current_revision_info['saved'] = sprintf($phrase, $revision->GetField('AutoSavedOn_time') . ' (' . $this->getAgoTime($auto_save_time) . ')'); } return $current_revision_info; } /** * Returns state of all CMS revision toolbar buttons. * * @param kDBItem $revision Page Revision. * * @return array */ public function getToolbarButtonsState(kDBItem $revision) { $ret = array(); foreach ( $this->getToolbarButtons() as $toolbar_button ) { $ret[$toolbar_button] = $this->isToolbarButtonEnabled($toolbar_button, $revision); } return $ret; } /** * Returns list of CMS revision toolbar buttons. * * @return array */ protected function getToolbarButtons() { return array('select', 'delete', 'approve', 'decline', 'preview', 'history'); } /** * Checks if given CMS revision toolbar button is enabled for given revision. * * @param string $button_name Toolbar button name. * @param kDBItem $revision Revision to check against. * * @return boolean */ protected function isToolbarButtonEnabled($button_name, kDBItem $revision) { $is_draft = $revision->GetDBField('IsDraft'); if ( $button_name == 'select' || $button_name == 'delete' || $button_name == 'preview' ) { return (bool)$is_draft; } if ( $button_name == 'approve' ) { return $revision->GetDBField('Status') != STATUS_ACTIVE && !$is_draft; } if ( $button_name == 'decline' ) { return $revision->GetDBField('Status') != STATUS_DISABLED && !$revision->GetDBField('IsLive') && !$is_draft; } return true; } /** * Returns warning to be shown in case of parallel editing attempts. * * @param array $users Users, that are editing a page. * * @return string */ protected function getEditorsWarning(array $users) { /** @var kMultiLanguageHelper $ml_helper */ $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); $ret = $ml_helper->getPluralPhrase( count($users), array( 'phrase1' => 'la_PageCurrentlyEditing1', 'phrase2' => 'la_PageCurrentlyEditing2', 'phrase5' => 'la_PageCurrentlyEditing5', ), false, true ); return sprintf($ret, implode(', ', $users)); } /** * Returns information about given page revisions. * * @param integer $page_id Page, that is being edited. * * @return array */ protected function getPageRevisions($page_id) { $ret = Array (); $tag_params = Array ('per_page' => -1, 'skip_parent_filter' => 1, 'requery' => 1, 'page_id' => $page_id); /** @var kDBList $revisions */ $revisions = $this->Application->recallObject('page-revision.list', 'page-revision_List', $tag_params); $revisions->Query(); $revisions->GoFirst(); while ( !$revisions->EOL() ) { $ret[ 'r' . $revisions->GetDBField('RevisionNumber') ] = array( 'title' => $this->getRevisionTitle($revisions), 'status' => $revisions->GetDBField('Status'), 'status_label' => $this->getRevisionStatusText($revisions), 'datetime' => $revisions->GetField('CreatedOn'), 'author' => $this->getRevisionAuthor($revisions), 'draft' => (int)$revisions->GetDBField('IsDraft'), ); $revisions->GoNext(); } return $ret; } /** * Returns title for given revision. * * @param kDBBase $revision Page Revision. * * @return string */ protected function getRevisionTitle(kDBBase $revision) { if ( $revision->GetDBField('IsDraft') ) { return $this->getAdminPhrase('la_Draft'); } $title = $this->getAdminPhrase('la_RevisionNumber'); return sprintf($title, $revision->GetDBField('RevisionNumber')); } /** * Returns status text for given revision. * * @param kDBBase $revision Page Revision. * * @return string */ protected function getRevisionStatusText(kDBBase $revision) { $status = $revision->GetDBField('Status'); $options = $revision->GetFieldOptions('Status'); return mb_strtolower($this->getAdminPhrase($options['options'][$status])); } /** * Returns author of given revision. * * @param kDBBase $revision Page Revision. * * @return string */ protected function getRevisionAuthor(kDBBase $revision) { $by_label = $this->getAdminPhrase('la_By'); return $by_label . ': ' . $revision->GetField('CreatedById'); } /** * Returns Admin's non-editable translation of given phrase. * * @param string $label Phrase label. * * @return string */ protected function getAdminPhrase($label) { return $this->Application->Phrase($label, false, true); } /** * Returns time passed between 2 given dates in "X minutes Y seconds ago" format * * @param int $from_date * @param int $to_date * @param integer $max_levels * * @return string */ public function getAgoTime($from_date, $to_date = null, $max_levels = 1) { $blocks = Array ( Array ('name' => 'year', 'amount' => 60*60*24*365), Array ('name' => 'month' ,'amount' => 60*60*24*31), Array ('name' => 'week', 'amount' => 60*60*24*7), Array ('name' => 'day', 'amount' => 60*60*24), Array ('name' => 'hour', 'amount' => 60*60), Array ('name' => 'minute', 'amount' => 60), Array ('name' => 'second', 'amount' => 1), ); if ( !isset($to_date) ) { $to_date = adodb_mktime(); } $diff = abs($to_date - $from_date); if ( $diff == 0 ) { return 'now'; } $current_level = 1; $result = Array (); foreach ($blocks as $block) { if ($current_level > $max_levels) { break; } if ( $diff / $block['amount'] >= 1 ) { $amount = floor($diff / $block['amount']); $plural = $amount > 1 ? 's' : ''; $result[] = $amount . ' ' . $block['name'] . $plural; $diff -= $amount * $block['amount']; $current_level++; } } return implode(' ', $result) . ' ago'; } /** * Returns where clause for loading correct revision for a given page * * @param int $page_id * @param int $live_revision_number * @param string $table_name * @return string */ public function getRevsionWhereClause($page_id, $live_revision_number, $table_name = '') { $revision = (int)$this->Application->GetVar('revision'); list ($user_id, $has_permission) = $this->getHistoryPermissionAndUser($page_id); if ( $has_permission && $revision ) { $revision_clause = $table_name . 'RevisionNumber = ' . $revision . ' AND ' . $table_name . 'IsDraft = 0'; } else { $editing_mode = $this->Application->GetVar('editing_mode'); // not in a EDITING_MODE constant, while in admin console $revision_clause = $table_name . 'RevisionNumber = ' . $live_revision_number . ' AND ' . $table_name . 'IsDraft = 0'; if ( $this->Application->ConfigValue('EnablePageContentRevisionControl') ) { if ( $this->Application->GetVar('preview') || $editing_mode == EDITING_MODE_CONTENT ) { $revision_clause = '(' . $table_name . 'CreatedById = ' . $user_id . ' AND ' . $table_name . 'IsDraft = 1) OR (' . $revision_clause . ')'; } } } return $revision_clause; } /** * Creates new content block in every revision that misses it. Plus creates first page revision * * @param int $page_id * @param int $num */ public function createNewContentBlock($page_id, $num) { $sql = 'SELECT pc.PageContentId, pr.RevisionId FROM ' . TABLE_PREFIX . 'PageRevisions pr LEFT JOIN ' . TABLE_PREFIX . 'PageContent pc ON pc.RevisionId = pr.RevisionId AND pc.ContentNum = ' . $num . ' WHERE pr.PageId = ' . $page_id; $revisions = $this->Conn->GetCol($sql, 'RevisionId'); if ( !$revisions ) { // no revisions for a page -> create a live revision /** @var kDBItem $revision */ $revision = $this->Application->recallObject('page-revision.live', null, Array ('skip_autoload' => true)); $revision->SetDBField('PageId', $page_id); $revision->SetDBField('RevisionNumber', 1); $revision->SetDBField('Status', STATUS_ACTIVE); $revision->Create(); $revisions[ $revision->GetID() ] = NULL; } /** @var kDBItem $content_block */ $content_block = $this->Application->recallObject('content.new', null, Array ('skip_autoload' => true)); $content_block->SetDBField('PageId', $page_id); $content_block->SetDBField('ContentNum', $num); foreach ($revisions as $revision_id => $content_block_id) { if ( is_numeric($content_block_id) ) { continue; } $content_block->SetDBField('RevisionId', $revision_id); $content_block->Create(); } } /** * Loads content block by it's number * * @param kDBItem $content_block * @param CategoriesItem $page * @param int $num * * @return bool */ public function loadContentBlock(&$content_block, &$page, $num) { $page_id = $page->GetID(); // Load all content blocks at once during regular page visits. if ( !EDITING_MODE && !$this->Application->GetVar('preview') ) { if ( !isset($this->contentBlockCache[$page_id]) ) { $where_clause = array( $content_block->TableName . '.PageId = ' . $page_id, 'pr.RevisionNumber = ' . $page->GetDBField('LiveRevisionNumber'), 'pr.IsDraft = 0', ); $sql = $content_block->GetSelectSQL() . ' WHERE (' . implode(') AND (', $where_clause) . ')'; $this->contentBlockCache[$page_id] = $this->Conn->Query($sql, 'ContentNum'); } if ( isset($this->contentBlockCache[$page_id][$num]) ) { $content_block->LoadFromHash($this->contentBlockCache[$page_id][$num]); } else { $content_block->Clear(); } return $content_block->isLoaded(); } // Load each content block individually with fallback to draft version, when editing content. $where_clause = array( $content_block->TableName . '.PageId = ' . $page_id, $content_block->TableName . '.ContentNum = ' . $num, $this->getRevsionWhereClause($page_id, $page->GetDBField('LiveRevisionNumber'), 'pr.'), ); $sql = $content_block->GetSelectSQL() . ' WHERE (' . implode(') AND (', $where_clause) . ') ORDER BY pr.IsDraft DESC, pr.RevisionNumber DESC'; $content_data = $this->Conn->GetRow($sql); $content_block->LoadFromHash($content_data); return $content_block->isLoaded(); } /** * Returns revision content * * @param integer $page_revision_id Page revision Id. * * @return array */ public function getRevisionContent($page_revision_id) { $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'PageContent WHERE RevisionId = ' . $page_revision_id; $blocks = $this->Conn->GetIterator($sql); /** @var kMultiLanguageHelper $ml_helper */ $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); $content = array(); foreach ( $ml_helper->getLanguages() as $lang_id ) { $parts = array(); foreach ( $blocks as $block_data ) { if ( (string)$block_data['l' . $lang_id . '_Content'] !== '' ) { $parts[] = $this->makeSearchable($block_data['l' . $lang_id . '_Content']); } } $content['l' . $lang_id . '_PageContent'] = implode(' ', $parts); } return $content; } /** * Unescapes and removes tags * * @param string $content Content. * * @return string */ protected function makeSearchable($content) { return trim(strip_tags(html_entity_decode($content, ENT_QUOTES, 'UTF-8'))); } }