/** Crosswords management.
  */
 function crossword($crossword = null, $operation = null)
 {
     if ('save' === $operation) {
         OutputModes('ajax');
     }
     if (!CheckPermissions('office')) {
         return;
     }
     if (null !== $crossword && is_numeric($crossword)) {
         $crossword = (int) $crossword;
         $crosswords = $this->crosswords_model->GetCrosswords($crossword);
         if (count($crosswords) === 0) {
             show_404();
         }
         $crossword_info = $crosswords[0];
         $this->load->model('permissions_model');
         $data = array('Permissions' => array('modify' => $this->permissions_model->hasUserPermission('CROSSWORD_MODIFY'), 'stats_basic' => $this->permissions_model->hasUserPermission('CROSSWORD_STATS_BASIC')), 'Crossword' => &$crossword_info);
         if (null === $operation) {
             if (!CheckRolePermissions('CROSSWORD_VIEW')) {
                 return;
             }
             $puzzle = 0;
             $worked = $this->crosswords_model->LoadCrossword($crossword_info['id'], $puzzle);
             if (!$worked) {
                 show_404();
             }
             $crosswordView = new CrosswordView($puzzle);
             $crosswordView->setClueTypes($crossword_info['has_quick_clues'], $crossword_info['has_cryptic_clues']);
             $crosswordView->setReadOnly(true, true);
             $data['Grid'] =& $crosswordView;
             $data['Tips'] = new CrosswordTipsList(null, $crossword_info['id'], true, false);
             $this->pages_model->SetPageCode('crosswords_office_xword_view');
             $this->main_frame->SetContentSimple('crosswords/office/crossword_view', $data);
         } else {
             if ('save' === $operation) {
                 if (!CheckRolePermissions('CROSSWORD_VIEW', 'CROSSWORD_MODIFY')) {
                     return;
                 }
                 if (isset($_POST['xw']['save'])) {
                     $puzzle = new CrosswordPuzzle();
                     $worked = $puzzle->importData($_POST['xw']);
                     if ($worked) {
                         $this->crosswords_model->SaveCrossword($crossword, $puzzle);
                         $status = 'success';
                     } else {
                         $this->main_frame->Error(array('class' => 'error', 'text' => 'Invalid crossword data.'));
                         $status = 'fail';
                     }
                 } else {
                     $this->main_frame->Error(array('class' => 'error', 'text' => 'Unable to edit crossword.'));
                     $status = 'fail';
                 }
                 $root = array('_tag' => 'crossword', 'status' => $status);
                 $this->main_frame->SetXml($root);
                 $this->main_frame->Load();
                 return;
             } else {
                 if ('edit' === $operation) {
                     if (!CheckRolePermissions('CROSSWORD_VIEW', 'CROSSWORD_MODIFY')) {
                         return;
                     }
                     $this->pages_model->SetPageCode('crosswords_office_xword_edit');
                     $this->load->helper('input_date');
                     $this->load->helper('input_progress');
                     $puzzle = 0;
                     $worked = $this->crosswords_model->LoadCrossword($crossword, $puzzle);
                     if (!$worked) {
                         $puzzle = new CrosswordPuzzle(13, 13);
                     }
                     $crosswordView = new CrosswordView($puzzle, true);
                     $data = array();
                     // MAIN CONFIGURATION
                     $config = new InputInterfaces();
                     $quick_clues_interface = new InputCheckboxInterface('has_quick_clues', $crossword_info['has_quick_clues']);
                     $config->Add('Quick clues', $quick_clues_interface);
                     $cryptic_clues_interface = new InputCheckboxInterface('has_cryptic_clues', $crossword_info['has_cryptic_clues']);
                     $config->Add('Cryptic Clues', $cryptic_clues_interface);
                     $categories = $this->crosswords_model->GetAllCategories();
                     $category_names = array();
                     foreach ($categories as $id => $category) {
                         $category_names[$id] = $category['name'];
                     }
                     $category_interface = new InputSelectInterface('category_id', $crossword_info['category_id']);
                     $category_interface->SetOptions($category_names);
                     $config->Add('Category', $category_interface);
                     $layouts = $this->crosswords_model->GetAllLayouts();
                     $layout_names = array();
                     foreach ($layouts as $id => $layout) {
                         $layout_names[$id] = $layout['name'];
                     }
                     $layout_interface = new InputSelectInterface('layout_id', $crossword_info['layout_id']);
                     $layout_interface->SetOptions($layout_names);
                     $config->Add('Layout', $layout_interface);
                     $deadline_interface = new InputDateInterface('deadline', $crossword_info['deadline'], true);
                     $config->Add('Deadline', $deadline_interface);
                     $publication_interface = new InputDateInterface('publication', $crossword_info['publication'], true);
                     $config->Add('Publication', $publication_interface);
                     $expiry_interface = new InputDateInterface('expiry', $crossword_info['expiry'], true);
                     $config->Add('Expiry', $expiry_interface);
                     $winners_value = $crossword_info['winners'];
                     $winners_interface = new InputIntInterface('winners', $winners_value, $winners_value > 0);
                     $winners_interface->SetRange(1, 100);
                     $config->Add('Winners', $winners_interface);
                     $completeness_interface = new InputProgressInterface('completeness', $crossword_info['completeness']);
                     $config->Add('Progress', $completeness_interface);
                     $authors_interface = new InputSelectInterface('authors', $crossword_info['author_ids']);
                     $authors = $this->crosswords_model->GetAllAuthors();
                     $author_options = array();
                     foreach ($authors as $author) {
                         $author_options[(int) $author['id']] = $author['fullname'];
                     }
                     foreach ($crossword_info['authors'] as $author) {
                         if (!isset($author_options[$author['id']])) {
                             $author_options[$author['id']] = $author['fullname'];
                         }
                     }
                     $authors_interface->SetOptions($author_options);
                     $config->Add('Authors', $authors_interface);
                     // VALIDATION
                     $num_errors = $config->Validate();
                     if (0 == $num_errors && $config->Updated()) {
                         $values = $config->ChangedValues();
                         $error = false;
                         if (count($values) == 0) {
                             $this->messages->AddMessage('information', "You did not make any changes");
                             $error = true;
                         }
                         // Apply rules to changes here
                         $integrated_values = $crossword_info;
                         foreach ($values as $id => $value) {
                             $integrated_values[$id] = $value;
                         }
                         // can't have deadline after publishing
                         if ($integrated_values['deadline'] !== null && $integrated_values['publication'] !== null && $integrated_values['deadline'] > $integrated_values['publication']) {
                             $this->messages->AddMessage('error', 'Deadline should not be set after publication');
                             $error = true;
                         }
                         // can't have expiry before publishing
                         if ($integrated_values['publication'] !== null && $integrated_values['expiry'] !== null && $integrated_values['publication'] > $integrated_values['expiry']) {
                             $this->messages->AddMessage('error', 'Expiry should not be set before publication');
                             $error = true;
                         }
                         if (!$error) {
                             if (isset($values['authors'])) {
                                 $authors = $values['authors'];
                                 $values['authors'] = array();
                                 foreach ($authors as $author_id) {
                                     $values['authors'][(int) $author_id] = array('id' => (int) $author_id, 'fullname' => $author_options[(int) $author_id]);
                                 }
                             }
                             $values['id'] = $crossword_info['id'];
                             if (!$this->crosswords_model->UpdateCrossword($values)) {
                                 $this->messages->AddMessage('error', 'Changes could not be saved');
                             } else {
                                 $this->messages->AddMessage('success', 'Changes have been saved successfully');
                                 foreach ($values as $id => $value) {
                                     $crossword_info[$id] = $value;
                                 }
                             }
                         }
                     }
                     // Which clues are enabled may have just changed
                     $crosswordView->setClueTypes($crossword_info['has_quick_clues'], $crossword_info['has_cryptic_clues']);
                     $data['Configuration'] =& $config;
                     $data['Tips'] = new CrosswordTipsList(null, $crossword_info['id'], true);
                     $data['Grid'] =& $crosswordView;
                     $data['Paths'] = array('view' => site_url("office/crosswords/crossword/{$crossword}"), 'save' => site_url("office/crosswords/crossword/{$crossword}/save"));
                     $this->main_frame->SetContentSimple('crosswords/office/crossword_edit', $data);
                 } else {
                     if ('stats' === $operation) {
                         if (!CheckRolePermissions('CROSSWORD_STATS_BASIC')) {
                             return;
                         }
                         // find information about this crossword
                         $data['Stats'] = $this->crosswords_model->CalculateStats($crossword_info['id'], array('saves', 'save_users'));
                         $data['Stats']['winners'] = $crossword_info['winners_so_far'];
                         $data['StatLabels'] = array('save_users' => 'Number of users who have attempted crossword (based on saves)', 'save_mean_per_user' => 'Mean number of saves per user (approx 30 secs between saves)', 'winners' => 'Number of winners');
                         $this->main_frame->SetContentSimple('crosswords/office/crossword_stats', $data);
                     } elseif ($operation == 'preview') {
                         if (!CheckRolePermissions('CROSSWORD_VIEW')) {
                             return;
                         }
                         $this->crosswords_model->GetCrosswordThumbnail($crossword);
                         return;
                     } else {
                         show_404();
                     }
                 }
             }
         }
     } else {
         show_404();
     }
     $this->main_frame->Load();
 }
 function crossword_by_id($id = null, $operation = null, $comment_include = null)
 {
     if ($operation == 'ajax') {
         OutputModes('ajax');
     }
     if (!CheckPermissions('public')) {
         return;
     }
     $loggedIn = $this->user_auth->isLoggedIn;
     if (null === $id || !is_numeric($id)) {
         show_404();
     }
     $id = (int) $id;
     $crossword = $this->crosswords_model->GetCrosswords($id, null, null, null, true, null);
     if (count($crossword) == 0) {
         show_404();
     }
     $crossword = $crossword[0];
     $puzzle = 0;
     $worked = $this->crosswords_model->LoadCrossword($crossword['id'], $puzzle);
     if (!$worked) {
         show_404();
     }
     // WARNING: SOLUTION NOT NECESSARILY CLEARED
     if (null === $operation || 'view' === $operation) {
         $puzzle->grid()->clearSolutions();
         $crosswordView = new CrosswordView($puzzle);
         $crosswordView->setClueTypes($crossword['has_quick_clues'], $crossword['has_cryptic_clues']);
         if (!$loggedIn) {
             $crosswordView->setReadOnly(!$crossword['expired']);
         } else {
             $success = $this->crosswords_model->LoadCrosswordVersion($crossword['id'], $this->user_auth->entityId, $puzzle);
         }
         // Comment thread
         $comments_thread = null;
         $comments_require_expiration = false;
         if ((!$comments_require_expiration || $crossword['expired']) && is_numeric($crossword['public_thread_id'])) {
             $this->load->library('comment_views');
             $this->comment_views->SetUri('/crosswords/' . $crossword['id'] . '/view/');
             $comments_thread = $this->comment_views->CreateStandard((int) $crossword['public_thread_id'], $comment_include);
         }
         $data = array();
         $data['Crossword'] =& $crossword;
         $data['Winners'] = $this->crosswords_model->GetWinners($crossword['id']);
         $data['Grid'] =& $crosswordView;
         $data['LoggedIn'] = $loggedIn;
         $data['Paths'] = array('ajax' => site_url('/crosswords/' . $crossword['id'] . '/ajax'));
         $data['Tips'] = new CrosswordTipsList(null, $crossword['id']);
         $data['Comments'] = $comments_thread;
         $data['Links'] = array('more crosswords in the "' . $crossword['category_name'] . '" category' => site_url('crosswords/' . $crossword['category_short_name']), 'crosswords home' => site_url('crosswords'));
         $data['ShareUrl'] = $_SERVER['SERVER_NAME'] . '/crosswords/' . $crossword['id'];
         $this->main_frame->SetContentSimple('crosswords/crossword', $data);
         /// @todo Use pages interface
         $pub = new Academic_time($crossword['publication']);
         $this->main_frame->SetTitle('Crossword ' . $pub->Format('D ') . $pub->AcademicTermNameUnique() . ' week ' . $pub->AcademicWeek());
         $this->main_frame->AddDescription('Online crossword');
         $this->main_frame->SetMainImage(site_url('crosswords/' . $crossword['id'] . '/preview'));
     } elseif ($operation == 'ajax') {
         $op2 = $comment_include;
         $root = array('_tag' => 'crossword');
         if ($op2 == 'winners') {
             $root['expired'] = $crossword['expired'] ? "yes" : "no";
             $root['positions'] = $crossword['winners'];
             $winners = $this->crosswords_model->GetWinners($crossword['id']);
             $root['winners'] = array();
             foreach ($winners as $position => &$winner) {
                 $root['winners'][] = array('_tag' => 'winner', '_attr' => array('position' => $position), 'name' => $winner['firstname'] . ' ' . $winner['surname']);
             }
         } elseif ($op2 == 'solution') {
             $root['solution'] = array('_attr' => array('available' => $crossword['expired'] ? "yes" : "no"));
             if ($crossword['expired']) {
                 $root['solution']['grid'] = array();
                 $grid =& $puzzle->grid();
                 $height = $grid->height();
                 $width = $grid->width();
                 for ($y = 0; $y < $height; ++$y) {
                     for ($x = 0; $x < $width; ++$x) {
                         $state = $grid->cellState($x, $y);
                         if (is_string($state)) {
                             $root['solution']['grid'][] = array('_tag' => 'letter', '_attr' => array('x' => $x, 'y' => $y), $state);
                         }
                     }
                 }
             }
         } elseif (!$loggedIn) {
             $this->main_frame->Error(array('class' => 'error', 'text' => 'Not logged in.'));
             $root['status'] = 'fail';
         } else {
             $puzzle->grid()->clearSolutions();
             $worked = $puzzle->importGrid($_POST['xw']);
             if ($worked) {
                 // Saving (and autosaving)
                 if (isset($_POST['xw']['save']) || isset($_POST['xw']['autosave'])) {
                     $success = $this->crosswords_model->SaveCrosswordVersion($crossword['id'], $this->user_auth->entityId, $puzzle);
                     $root['status'] = $success ? 'success' : 'fail';
                     if (!$success) {
                         $this->main_frame->Error(array('class' => 'error', 'text' => 'Unable to save crossword.'));
                     }
                 } elseif (isset($_POST['xw']['submit'])) {
                     $root['status'] = 'success';
                     $correct = $puzzle->isCorrect();
                     $root['mark'] = $correct ? 'correct' : 'incorrect';
                     $winner = false;
                     if ($correct) {
                         $winner = $this->crosswords_model->AddWinner($crossword['id'], $this->user_auth->entityId);
                     }
                     $root['winner'] = $winner ? 'yes' : 'no';
                 } else {
                     $this->main_frame->Error(array('class' => 'error', 'text' => 'Unable to edit crossword.'));
                     $root['status'] = 'fail';
                 }
             } else {
                 $this->main_frame->Error(array('class' => 'error', 'text' => 'Invalid crossword data.'));
                 $root['status'] = 'fail';
             }
         }
         $this->main_frame->SetXml($root);
     } elseif ($operation == 'preview') {
         $this->crosswords_model->GetCrosswordThumbnail($crossword['id']);
         return;
     } else {
         show_404();
     }
     $this->main_frame->Load();
 }