| Total Complexity | 132 |
| Total Lines | 1084 |
| Duplicated Lines | 0 % |
| Changes | 8 | ||
| Bugs | 0 | Features | 0 |
Complex classes like ManagePortalArticles_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ManagePortalArticles_Controller, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 19 | class ManagePortalArticles_Controller extends Action_Controller |
||
| 20 | { |
||
| 21 | /** @var bool|int hold the article id if existing */ |
||
| 22 | protected $_is_aid; |
||
| 23 | |||
| 24 | /** @var array */ |
||
| 25 | protected $_attachments; |
||
| 26 | |||
| 27 | /** @var ErrorContext */ |
||
| 28 | protected $article_errors; |
||
| 29 | |||
| 30 | /** @var AttachmentErrorContext */ |
||
| 31 | protected $attach_errors; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * This method is executed before any action handler. |
||
| 35 | * Loads common things for all methods |
||
| 36 | */ |
||
| 37 | public function pre_dispatch() |
||
| 38 | { |
||
| 39 | // We'll need the utility functions from here. |
||
| 40 | require_once(SUBSDIR . '/PortalAdmin.subs.php'); |
||
| 41 | require_once(SUBSDIR . '/Portal.subs.php'); |
||
| 42 | require_once(SUBSDIR . '/PortalArticle.subs.php'); |
||
| 43 | } |
||
| 44 | |||
| 45 | /** |
||
| 46 | * Main article dispatcher. |
||
| 47 | * |
||
| 48 | * This function checks permissions and passes control through. |
||
| 49 | */ |
||
| 50 | public function action_index() |
||
| 51 | { |
||
| 52 | global $context, $txt; |
||
| 53 | |||
| 54 | // You need to be an admin or have manage article permissions |
||
| 55 | if (!allowedTo('sp_admin')) |
||
| 56 | { |
||
| 57 | isAllowedTo('sp_manage_articles'); |
||
| 58 | } |
||
| 59 | |||
| 60 | loadTemplate('PortalAdminArticles'); |
||
| 61 | |||
| 62 | // These are all the actions that we know |
||
| 63 | $subActions = array( |
||
| 64 | 'list' => array($this, 'action_list'), |
||
| 65 | 'add' => array($this, 'action_edit'), |
||
| 66 | 'edit' => array($this, 'action_edit'), |
||
| 67 | 'status' => array($this, 'action_status'), |
||
| 68 | 'delete' => array($this, 'action_delete'), |
||
| 69 | ); |
||
| 70 | |||
| 71 | // Start up the controller, provide a hook since we can |
||
| 72 | $action = new Action('portal_articles'); |
||
| 73 | |||
| 74 | // Set up the tab data |
||
| 75 | $context[$context['admin_menu_name']]['tab_data'] = array( |
||
| 76 | 'title' => $txt['sp_admin_articles_title'], |
||
| 77 | 'help' => 'sp_ArticlesArea', |
||
| 78 | 'description' => $txt['sp_admin_articles_desc'], |
||
| 79 | 'tabs' => array( |
||
| 80 | 'list' => array(), |
||
| 81 | 'add' => array(), |
||
| 82 | ), |
||
| 83 | ); |
||
| 84 | |||
| 85 | // By default we want to list articles |
||
| 86 | $subAction = $action->initialize($subActions, 'list'); |
||
| 87 | $context['sub_action'] = $subAction; |
||
| 88 | |||
| 89 | // Call the right function for this sub-action |
||
| 90 | $action->dispatch($subAction); |
||
| 91 | } |
||
| 92 | |||
| 93 | /** |
||
| 94 | * Show a listing of articles in the system |
||
| 95 | */ |
||
| 96 | public function action_list() |
||
| 97 | { |
||
| 98 | global $context, $scripturl, $txt, $modSettings; |
||
| 99 | |||
| 100 | // Build the listoption array to display the categories |
||
| 101 | $listOptions = array( |
||
| 102 | 'id' => 'portal_articles', |
||
| 103 | 'title' => $txt['sp_admin_articles_list'], |
||
| 104 | 'items_per_page' => $modSettings['defaultMaxMessages'], |
||
| 105 | 'no_items_label' => $txt['error_sp_no_articles'], |
||
| 106 | 'base_href' => $scripturl . '?action=admin;area=portalarticles;sa=list;', |
||
| 107 | 'default_sort_col' => 'title', |
||
| 108 | 'get_items' => array( |
||
| 109 | 'function' => array($this, 'list_spLoadArticles'), |
||
| 110 | ), |
||
| 111 | 'get_count' => array( |
||
| 112 | 'function' => array($this, 'list_spCountArticles'), |
||
| 113 | ), |
||
| 114 | 'columns' => array( |
||
| 115 | 'title' => array( |
||
| 116 | 'header' => array( |
||
| 117 | 'value' => $txt['sp_admin_articles_col_title'], |
||
| 118 | ), |
||
| 119 | 'data' => array( |
||
| 120 | 'db' => 'title', |
||
| 121 | ), |
||
| 122 | 'sort' => array( |
||
| 123 | 'default' => 'title', |
||
| 124 | 'reverse' => 'title DESC', |
||
| 125 | ), |
||
| 126 | ), |
||
| 127 | 'namespace' => array( |
||
| 128 | 'header' => array( |
||
| 129 | 'value' => $txt['sp_admin_articles_col_namespace'], |
||
| 130 | ), |
||
| 131 | 'data' => array( |
||
| 132 | 'db' => 'article_id', |
||
| 133 | ), |
||
| 134 | 'sort' => array( |
||
| 135 | 'default' => 'article_namespace', |
||
| 136 | 'reverse' => 'article_namespace DESC', |
||
| 137 | ), |
||
| 138 | ), |
||
| 139 | 'category' => array( |
||
| 140 | 'header' => array( |
||
| 141 | 'value' => $txt['sp_admin_articles_col_category'], |
||
| 142 | ), |
||
| 143 | 'data' => array( |
||
| 144 | 'db' => 'category_name', |
||
| 145 | ), |
||
| 146 | 'sort' => array( |
||
| 147 | 'default' => 'name', |
||
| 148 | 'reverse' => 'name DESC', |
||
| 149 | ), |
||
| 150 | ), |
||
| 151 | 'author' => array( |
||
| 152 | 'header' => array( |
||
| 153 | 'value' => $txt['sp_admin_articles_col_author'], |
||
| 154 | ), |
||
| 155 | 'data' => array( |
||
| 156 | 'db' => 'author_name', |
||
| 157 | ), |
||
| 158 | 'sort' => array( |
||
| 159 | 'default' => 'author_name', |
||
| 160 | 'reverse' => 'author_name DESC', |
||
| 161 | ), |
||
| 162 | ), |
||
| 163 | 'type' => array( |
||
| 164 | 'header' => array( |
||
| 165 | 'value' => $txt['sp_admin_articles_col_type'], |
||
| 166 | ), |
||
| 167 | 'data' => array( |
||
| 168 | 'db' => 'type', |
||
| 169 | ), |
||
| 170 | 'sort' => array( |
||
| 171 | 'default' => 'type', |
||
| 172 | 'reverse' => 'type DESC', |
||
| 173 | ), |
||
| 174 | ), |
||
| 175 | 'date' => array( |
||
| 176 | 'header' => array( |
||
| 177 | 'value' => $txt['sp_admin_articles_col_date'], |
||
| 178 | ), |
||
| 179 | 'data' => array( |
||
| 180 | 'db' => 'date', |
||
| 181 | ), |
||
| 182 | 'sort' => array( |
||
| 183 | 'default' => 'date', |
||
| 184 | 'reverse' => 'date DESC', |
||
| 185 | ), |
||
| 186 | ), |
||
| 187 | 'status' => array( |
||
| 188 | 'header' => array( |
||
| 189 | 'value' => $txt['sp_admin_articles_col_status'], |
||
| 190 | 'class' => 'centertext', |
||
| 191 | ), |
||
| 192 | 'data' => array( |
||
| 193 | 'db' => 'status_image', |
||
| 194 | 'class' => 'centertext', |
||
| 195 | ), |
||
| 196 | 'sort' => array( |
||
| 197 | 'default' => 'status', |
||
| 198 | 'reverse' => 'status DESC', |
||
| 199 | ), |
||
| 200 | ), |
||
| 201 | 'action' => array( |
||
| 202 | 'header' => array( |
||
| 203 | 'value' => $txt['sp_admin_articles_col_actions'], |
||
| 204 | 'class' => 'centertext', |
||
| 205 | ), |
||
| 206 | 'data' => array( |
||
| 207 | 'sprintf' => array( |
||
| 208 | 'format' => ' |
||
| 209 | <a href="?action=admin;area=portalarticles;sa=edit;article_id=%1$s;' . $context['session_var'] . '=' . $context['session_id'] . '" accesskey="e">' . sp_embed_image('modify') . '</a> |
||
| 210 | <a href="?action=admin;area=portalarticles;sa=delete;article_id=%1$s;' . $context['session_var'] . '=' . $context['session_id'] . '" onclick="return confirm(' . JavaScriptEscape($txt['sp_admin_articles_delete_confirm']) . ') && submitThisOnce(this);" accesskey="d">' . sp_embed_image('delete') . '</a>', |
||
| 211 | 'params' => array( |
||
| 212 | 'id' => true, |
||
| 213 | ), |
||
| 214 | ), |
||
| 215 | 'class' => 'centertext nowrap', |
||
| 216 | ), |
||
| 217 | ), |
||
| 218 | 'check' => array( |
||
| 219 | 'header' => array( |
||
| 220 | 'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />', |
||
| 221 | 'class' => 'centertext', |
||
| 222 | ), |
||
| 223 | 'data' => array( |
||
| 224 | 'function' => function ($row) |
||
| 225 | { |
||
| 226 | return '<input type="checkbox" name="remove[]" value="' . $row['id'] . '" class="input_check" />'; |
||
| 227 | }, |
||
| 228 | 'class' => 'centertext', |
||
| 229 | ), |
||
| 230 | ), |
||
| 231 | ), |
||
| 232 | 'form' => array( |
||
| 233 | 'href' => $scripturl . '?action=admin;area=portalarticles;sa=delete', |
||
| 234 | 'include_sort' => true, |
||
| 235 | 'include_start' => true, |
||
| 236 | 'hidden_fields' => array( |
||
| 237 | $context['session_var'] => $context['session_id'], |
||
| 238 | ), |
||
| 239 | ), |
||
| 240 | 'additional_rows' => array( |
||
| 241 | array( |
||
| 242 | 'class' => 'submitbutton', |
||
| 243 | 'position' => 'below_table_data', |
||
| 244 | 'value' => '<a class="linkbutton" href="?action=admin;area=portalarticles;sa=add;' . $context['session_var'] . '=' . $context['session_id'] . '" accesskey="a">' . $txt['sp_admin_articles_add'] . '</a> |
||
| 245 | <input type="submit" name="remove_articles" value="' . $txt['sp_admin_articles_remove'] . '" />', |
||
| 246 | ), |
||
| 247 | ), |
||
| 248 | ); |
||
| 249 | |||
| 250 | // Set the context values |
||
| 251 | $context['page_title'] = $txt['sp_admin_articles_title']; |
||
| 252 | $context['sub_template'] = 'show_list'; |
||
| 253 | $context['default_list'] = 'portal_articles'; |
||
| 254 | |||
| 255 | // Create the list. |
||
| 256 | require_once(SUBSDIR . '/GenericList.class.php'); |
||
| 257 | createList($listOptions); |
||
| 258 | } |
||
| 259 | |||
| 260 | /** |
||
| 261 | * Callback for createList(), |
||
| 262 | * Returns the number of articles in the system |
||
| 263 | */ |
||
| 264 | public function list_spCountArticles() |
||
| 267 | } |
||
| 268 | |||
| 269 | /** |
||
| 270 | * Callback for createList() |
||
| 271 | * Returns an array of articles |
||
| 272 | * |
||
| 273 | * @param int $start |
||
| 274 | * @param int $items_per_page |
||
| 275 | * @param string $sort |
||
| 276 | * |
||
| 277 | * @return array |
||
| 278 | */ |
||
| 279 | public function list_spLoadArticles($start, $items_per_page, $sort) |
||
| 280 | { |
||
| 281 | return sp_load_articles($start, $items_per_page, $sort); |
||
| 282 | } |
||
| 283 | |||
| 284 | /** |
||
| 285 | * Edits an existing OR adds a new article to the system |
||
| 286 | * |
||
| 287 | * - Handles the previewing of an article |
||
| 288 | * - Handles article attachments |
||
| 289 | */ |
||
| 290 | public function action_edit() |
||
| 370 | } |
||
| 371 | |||
| 372 | /** |
||
| 373 | * Loads in dependency's for saving or editing an article |
||
| 374 | */ |
||
| 375 | private function editInit() |
||
| 395 | } |
||
| 396 | |||
| 397 | /** |
||
| 398 | * Does the actual saving of the article data |
||
| 399 | * |
||
| 400 | * - Validates the data is safe to save |
||
| 401 | * - Updates existing articles or creates new ones |
||
| 402 | */ |
||
| 403 | private function _sportal_admin_article_edit_save() |
||
| 404 | { |
||
| 405 | global $context, $txt, $modSettings; |
||
| 406 | |||
| 407 | // Use our standard validation functions in a few spots |
||
| 408 | require_once(SUBSDIR . '/DataValidator.class.php'); |
||
| 409 | $validator = new Data_Validator(); |
||
| 410 | |||
| 411 | // If it exists, load the current data |
||
| 412 | if ($this->_is_aid) |
||
| 413 | { |
||
| 414 | $_POST['article_id'] = $this->_is_aid; |
||
| 415 | $context['article'] = sportal_get_articles($this->_is_aid); |
||
|
|
|||
| 416 | } |
||
| 417 | |||
| 418 | // Clean and Review the post data for compliance |
||
| 419 | $validator->sanitation_rules(array( |
||
| 420 | 'title' => 'trim|Util::htmlspecialchars', |
||
| 421 | 'namespace' => 'trim|Util::htmlspecialchars', |
||
| 422 | 'article_id' => 'intval', |
||
| 423 | 'category_id' => 'intval', |
||
| 424 | 'permissions' => 'intval', |
||
| 425 | 'styles' => 'intval', |
||
| 426 | 'type' => 'trim', |
||
| 427 | 'content' => 'trim' |
||
| 428 | )); |
||
| 429 | $validator->validation_rules(array( |
||
| 430 | 'title' => 'required', |
||
| 431 | 'namespace' => 'alpha_numeric|required', |
||
| 432 | 'type' => 'required', |
||
| 433 | 'content' => 'required' |
||
| 434 | )); |
||
| 435 | $validator->text_replacements(array( |
||
| 436 | 'title' => $txt['sp_admin_articles_col_title'], |
||
| 437 | 'namespace' => $txt['sp_admin_articles_col_namespace'], |
||
| 438 | 'content' => $txt['sp_admin_articles_col_body'] |
||
| 439 | )); |
||
| 440 | |||
| 441 | // If you messed this up, tell them why |
||
| 442 | if (!$validator->validate($_POST)) |
||
| 443 | { |
||
| 444 | foreach ($validator->validation_errors() as $id => $error) |
||
| 445 | { |
||
| 446 | $this->article_errors->addError($error); |
||
| 447 | } |
||
| 448 | } |
||
| 449 | |||
| 450 | // Lets make sure this namespace (article id) is unique |
||
| 451 | $has_duplicate = sp_duplicate_articles($validator->article_id, $validator->namespace); |
||
| 452 | if (!empty($has_duplicate)) |
||
| 453 | { |
||
| 454 | $this->article_errors->addError('sp_error_article_namespace_duplicate'); |
||
| 455 | } |
||
| 456 | |||
| 457 | // And we can't have just a numeric namespace (article id) |
||
| 458 | if (preg_replace('~[0-9]+~', '', $validator->namespace) === '') |
||
| 459 | { |
||
| 460 | $this->article_errors->addError('sp_error_article_namespace_numeric'); |
||
| 461 | } |
||
| 462 | |||
| 463 | // Posting some PHP code, and allowed? Then we need to validate it will run |
||
| 464 | if ($_POST['type'] === 'php' && !empty($_POST['content']) && empty($modSettings['sp_disable_php_validation'])) |
||
| 465 | { |
||
| 466 | $validator_php = new Data_Validator(); |
||
| 467 | $validator_php->validation_rules(array('content' => 'php_syntax')); |
||
| 468 | |||
| 469 | // Bad PHP code |
||
| 470 | if (!$validator_php->validate(array('content' => $_POST['content']))) |
||
| 471 | { |
||
| 472 | $this->article_errors->addError($validator_php->validation_errors()); |
||
| 473 | } |
||
| 474 | } |
||
| 475 | |||
| 476 | // Check / Prep attachments |
||
| 477 | $this->processArticleAttachments(); |
||
| 478 | |||
| 479 | // None shall pass ... with errors |
||
| 480 | if ($this->article_errors->hasErrors() || $this->attach_errors->hasErrors()) |
||
| 481 | { |
||
| 482 | unset($_POST['submit']); |
||
| 483 | |||
| 484 | return false; |
||
| 485 | } |
||
| 486 | |||
| 487 | // No errors then, prepare the data for saving |
||
| 488 | $article_info = array( |
||
| 489 | 'id' => $validator->article_id, |
||
| 490 | 'id_category' => $validator->category_id, |
||
| 491 | 'namespace' => $validator->namespace, |
||
| 492 | 'title' => $validator->title, |
||
| 493 | 'body' => Util::htmlspecialchars($_POST['content'], ENT_QUOTES), |
||
| 494 | 'type' => in_array($validator->type, array('bbc', 'html', 'php')) ? $_POST['type'] : 'bbc', |
||
| 495 | 'permissions' => $validator->permissions, |
||
| 496 | 'styles' => $validator->styles, |
||
| 497 | 'status' => !empty($_POST['status']) ? 1 : 0, |
||
| 498 | ); |
||
| 499 | |||
| 500 | if ($article_info['type'] === 'bbc') |
||
| 501 | { |
||
| 502 | preparsecode($article_info['body']); |
||
| 503 | } |
||
| 504 | |||
| 505 | // Bind attachments to the article, create any needed thumbnails, move to sp attachment directory |
||
| 506 | $this->finalizeArticleAttachments($article_info); |
||
| 507 | |||
| 508 | // Save away |
||
| 509 | checkSession(); |
||
| 510 | $this->_is_aid = sp_save_article($article_info, empty($this->_is_aid)); |
||
| 511 | |||
| 512 | // And return to the listing |
||
| 513 | redirectexit('action=admin;area=portalarticles'); |
||
| 514 | } |
||
| 515 | |||
| 516 | /** |
||
| 517 | * Save attachments based on the form inputs |
||
| 518 | * |
||
| 519 | * - Remove existing ones that have been "unchecked" in the form |
||
| 520 | * - Performs security, size, type, etc checks |
||
| 521 | * - Moves files to the current attachment directory, we will move it again to sp attachment in |
||
| 522 | * the following steps. |
||
| 523 | */ |
||
| 524 | private function processArticleAttachments() |
||
| 525 | { |
||
| 526 | global $user_info, $context, $modSettings; |
||
| 527 | |||
| 528 | // First see if they are trying to delete current attachments. |
||
| 529 | if (isset($_POST['attach_del'])) |
||
| 530 | { |
||
| 531 | $keep_temp = array(); |
||
| 532 | $keep_ids = array(); |
||
| 533 | foreach ($_POST['attach_del'] as $idRemove) |
||
| 534 | { |
||
| 535 | $attachID = getAttachmentIdFromPublic($idRemove); |
||
| 536 | |||
| 537 | if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false) |
||
| 538 | { |
||
| 539 | $keep_temp[] = $attachID; |
||
| 540 | } |
||
| 541 | else |
||
| 542 | { |
||
| 543 | $keep_ids[] = (int) $attachID; |
||
| 544 | } |
||
| 545 | } |
||
| 546 | |||
| 547 | if (isset($_SESSION['temp_attachments'])) |
||
| 548 | { |
||
| 549 | foreach ($_SESSION['temp_attachments'] as $attachID => $attachment) |
||
| 550 | { |
||
| 551 | if ((isset($_SESSION['temp_attachments']['post']['files'], $attachment['name']) |
||
| 552 | && in_array($attachment['name'], $_SESSION['temp_attachments']['post']['files'])) |
||
| 553 | || in_array($attachID, $keep_temp) |
||
| 554 | || strpos($attachID, 'post_tmp_' . $user_info['id']) === false |
||
| 555 | ) |
||
| 556 | { |
||
| 557 | continue; |
||
| 558 | } |
||
| 559 | |||
| 560 | unset($_SESSION['temp_attachments'][$attachID]); |
||
| 561 | @unlink($attachment['tmp_name']); |
||
| 562 | } |
||
| 563 | } |
||
| 564 | |||
| 565 | if (!empty($this->_is_aid)) |
||
| 566 | { |
||
| 567 | $attachmentQuery = array( |
||
| 568 | 'id_article' => $this->_is_aid, |
||
| 569 | 'not_id_attach' => $keep_ids, |
||
| 570 | 'id_folder' => $modSettings['sp_articles_attachment_dir'], |
||
| 571 | ); |
||
| 572 | removeArticleAttachments($attachmentQuery); |
||
| 573 | } |
||
| 574 | } |
||
| 575 | |||
| 576 | // Upload any new attachments. |
||
| 577 | $context['attachments']['can']['post'] = (allowedTo('post_attachment') |
||
| 578 | || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments'))); |
||
| 579 | if ($context['attachments']['can']['post']) |
||
| 580 | { |
||
| 581 | list($context['attachments']['quantity'], $context['attachments']['total_size']) = attachmentsSizeForArticle($this->_is_aid); |
||
| 582 | processAttachments(); |
||
| 583 | } |
||
| 584 | } |
||
| 585 | |||
| 586 | /** |
||
| 587 | * Handle the final processing of attachments, including any thumbnail generation |
||
| 588 | * and linking attachments to the specific article. Saves the articles in the SP |
||
| 589 | * attachment directory. |
||
| 590 | */ |
||
| 591 | private function finalizeArticleAttachments(&$article_info) |
||
| 592 | { |
||
| 593 | global $context, $user_info, $modSettings, $ignore_temp; |
||
| 594 | |||
| 595 | $attachIDs = array(); |
||
| 596 | if (empty($ignore_temp) && $context['attachments']['can']['post'] && !empty($_SESSION['temp_attachments'])) |
||
| 597 | { |
||
| 598 | foreach ($_SESSION['temp_attachments'] as $attachID => $attachment) |
||
| 599 | { |
||
| 600 | if ($attachID !== 'initial_error' && strpos($attachID, 'post_tmp_' . $user_info['id']) === false) |
||
| 601 | { |
||
| 602 | continue; |
||
| 603 | } |
||
| 604 | |||
| 605 | // If there was an initial error just show that message. |
||
| 606 | if ($attachID === 'initial_error') |
||
| 607 | { |
||
| 608 | unset($_SESSION['temp_attachments']); |
||
| 609 | break; |
||
| 610 | } |
||
| 611 | |||
| 612 | // No errors, then try to create the attachment |
||
| 613 | if (empty($attachment['errors'])) |
||
| 614 | { |
||
| 615 | // Load the attachmentOptions array with the data needed to create an attachment |
||
| 616 | $attachmentOptions = array( |
||
| 617 | 'article' => !empty($this->_is_aid) ? $this->_is_aid : 0, |
||
| 618 | 'poster' => $user_info['id'], |
||
| 619 | 'name' => $attachment['name'], |
||
| 620 | 'tmp_name' => $attachment['tmp_name'], |
||
| 621 | 'size' => $attachment['size'] ?? 0, |
||
| 622 | 'mime_type' => $attachment['type'] ?? '', |
||
| 623 | 'id_folder' => $modSettings['sp_articles_attachment_dir'], |
||
| 624 | 'approved' => true, |
||
| 625 | 'errors' => array(), |
||
| 626 | ); |
||
| 627 | |||
| 628 | if (createArticleAttachment($attachmentOptions)) |
||
| 629 | { |
||
| 630 | $attachIDs[] = $attachmentOptions['id']; |
||
| 631 | if (!empty($attachmentOptions['thumb'])) |
||
| 632 | { |
||
| 633 | $attachIDs[] = $attachmentOptions['thumb']; |
||
| 634 | } |
||
| 635 | |||
| 636 | // Replace ila attach tags with the new valid attachment id and [spattach] tag |
||
| 637 | $article_info['body'] = preg_replace('~\[attach(.*?)\]' . $attachment['public_attachid'] . '\[\/attach\]~', '[spattach$1]' . $attachmentOptions['id'] . '[/spattach]', $article_info['body']); |
||
| 638 | } |
||
| 639 | } |
||
| 640 | // We have errors on this file, build out the issues for display to the user |
||
| 641 | else |
||
| 642 | { |
||
| 643 | @unlink($attachment['tmp_name']); |
||
| 644 | } |
||
| 645 | } |
||
| 646 | |||
| 647 | unset($_SESSION['temp_attachments']); |
||
| 648 | } |
||
| 649 | |||
| 650 | return $attachIDs; |
||
| 651 | } |
||
| 652 | |||
| 653 | /** |
||
| 654 | * Setup the add/edit article template values |
||
| 655 | */ |
||
| 656 | private function prepareArticleForm() |
||
| 657 | { |
||
| 658 | global $txt, $context; |
||
| 659 | |||
| 660 | $context['attachments']['current'] = array(); |
||
| 661 | |||
| 662 | // Just taking a look before you save, or tried to save with errors? |
||
| 663 | if (!empty($_POST['preview']) || $this->article_errors->hasErrors() || $this->attach_errors->hasErrors()) |
||
| 664 | { |
||
| 665 | $context['article'] = $this->_sportal_admin_article_preview(); |
||
| 666 | |||
| 667 | // If there are attachment errors. Let's show a list to the user. |
||
| 668 | if ($this->attach_errors->hasErrors()) |
||
| 669 | { |
||
| 670 | loadTemplate('Errors'); |
||
| 671 | $errors = $this->attach_errors->prepareErrors(); |
||
| 672 | foreach ($errors as $key => $error) |
||
| 673 | { |
||
| 674 | $context['attachment_error_keys'][] = $key . '_error'; |
||
| 675 | $context[$key . '_error'] = $error; |
||
| 676 | } |
||
| 677 | } |
||
| 678 | |||
| 679 | // Showing errors or a preview? |
||
| 680 | if ($this->article_errors->hasErrors()) |
||
| 681 | { |
||
| 682 | $context['article_errors'] = array( |
||
| 683 | 'errors' => $this->article_errors->prepareErrors(), |
||
| 684 | 'type' => $this->article_errors->getErrorType() == 0 ? 'minor' : 'serious', |
||
| 685 | 'title' => $txt['sp_form_errors_detected'], |
||
| 686 | ); |
||
| 687 | } |
||
| 688 | |||
| 689 | // Preview needs a flag |
||
| 690 | if (!empty($_POST['preview'])) |
||
| 691 | { |
||
| 692 | // We reuse this template for the preview |
||
| 693 | loadTemplate('PortalArticles'); |
||
| 694 | $context['preview'] = true; |
||
| 695 | } |
||
| 696 | } |
||
| 697 | // Something new? |
||
| 698 | elseif (!$this->_is_aid) |
||
| 699 | { |
||
| 700 | $context['article'] = array( |
||
| 701 | 'id' => 0, |
||
| 702 | 'article_id' => 'article' . random_int(1, 5000), |
||
| 703 | 'category' => array('id' => 0), |
||
| 704 | 'title' => $txt['sp_articles_default_title'], |
||
| 705 | 'body' => '', |
||
| 706 | 'type' => 'bbc', |
||
| 707 | 'permissions' => 3, |
||
| 708 | 'styles' => 4, |
||
| 709 | 'status' => 1, |
||
| 710 | ); |
||
| 711 | } |
||
| 712 | // Something used |
||
| 713 | else |
||
| 714 | { |
||
| 715 | $_REQUEST['article_id'] = $this->_is_aid; |
||
| 716 | $context['article'] = sportal_get_articles($this->_is_aid); |
||
| 717 | $attach = sportal_get_articles_attachments($this->_is_aid, true); |
||
| 718 | $context['attachments']['current'] = !empty($attach[$this->_is_aid]) ? $attach[$this->_is_aid] : array(); |
||
| 719 | } |
||
| 720 | } |
||
| 721 | |||
| 722 | /** |
||
| 723 | * Sets up for an article preview |
||
| 724 | */ |
||
| 725 | private function _sportal_admin_article_preview() |
||
| 726 | { |
||
| 727 | global $scripturl, $user_info; |
||
| 728 | |||
| 729 | // Existing article will have some data |
||
| 730 | if ($this->_is_aid) |
||
| 731 | { |
||
| 732 | $_POST['article_id'] = $this->_is_aid; |
||
| 733 | $current = sportal_get_articles($this->_is_aid); |
||
| 734 | $author = $current['author']; |
||
| 735 | $date = standardTime($current['date']); |
||
| 736 | list($views, $comments) = sportal_get_article_views_comments($this->_is_aid); |
||
| 737 | } |
||
| 738 | // New ones we set defaults |
||
| 739 | else |
||
| 740 | { |
||
| 741 | $author = array('link' => '<a href="' . $scripturl . '?action=profile;u=' . $user_info['id'] . '">' . $user_info['name'] . '</a>'); |
||
| 742 | $date = standardTime(time()); |
||
| 743 | $views = 0; |
||
| 744 | $comments = 0; |
||
| 745 | } |
||
| 746 | |||
| 747 | $article = array( |
||
| 748 | 'id' => $_POST['article_id'], |
||
| 749 | 'article_id' => $_POST['namespace'], |
||
| 750 | 'category' => sportal_get_categories((int) $_POST['category_id']), |
||
| 751 | 'author' => $author, |
||
| 752 | 'title' => Util::htmlspecialchars($_POST['title'], ENT_QUOTES), |
||
| 753 | 'body' => Util::htmlspecialchars($_POST['content'], ENT_QUOTES), |
||
| 754 | 'type' => $_POST['type'], |
||
| 755 | 'permissions' => $_POST['permissions'], |
||
| 756 | 'styles' => $_POST['styles'], |
||
| 757 | 'date' => $date, |
||
| 758 | 'status' => !empty($_POST['status']), |
||
| 759 | 'view_count' => $views, |
||
| 760 | 'comment_count' => $comments, |
||
| 761 | ); |
||
| 762 | |||
| 763 | if ($article['type'] === 'bbc') |
||
| 764 | { |
||
| 765 | preparsecode($article['body']); |
||
| 766 | } |
||
| 767 | |||
| 768 | return $article; |
||
| 769 | } |
||
| 770 | |||
| 771 | /** |
||
| 772 | * Sets up editor options as needed for SP, temporarily ignores any user options |
||
| 773 | * so we can enable it in the proper mode bbc/php/html |
||
| 774 | */ |
||
| 775 | private function prepareEditor() |
||
| 776 | { |
||
| 777 | global $context, $options; |
||
| 778 | |||
| 779 | if ($context['article']['type'] !== 'bbc') |
||
| 780 | { |
||
| 781 | // Override user preferences for wizzy mode if they don't need it |
||
| 782 | $temp_editor = !empty($options['wysiwyg_default']); |
||
| 783 | $options['wysiwyg_default'] = false; |
||
| 784 | } |
||
| 785 | |||
| 786 | // Fire up the editor with the values |
||
| 787 | $editor_options = array( |
||
| 788 | 'id' => 'content', |
||
| 789 | 'value' => $context['article']['body'], |
||
| 790 | 'width' => '100%', |
||
| 791 | 'height' => '275px', |
||
| 792 | 'preview_type' => 2, |
||
| 793 | ); |
||
| 794 | create_control_richedit($editor_options); |
||
| 795 | $context['post_box_name'] = $editor_options['id']; |
||
| 796 | $context['attached'] = ''; |
||
| 797 | |||
| 798 | // Restore their settings |
||
| 799 | if (isset($temp_editor)) |
||
| 800 | { |
||
| 801 | $options['wysiwyg_default'] = $temp_editor; |
||
| 802 | } |
||
| 803 | } |
||
| 804 | |||
| 805 | /** |
||
| 806 | * Loads in permission, visibility and style profiles for use in the template |
||
| 807 | * If unable to load profiles, simply dies as "somethings broke"tm |
||
| 808 | */ |
||
| 809 | private function loadProfileContext() |
||
| 810 | { |
||
| 811 | global $context; |
||
| 812 | |||
| 813 | $context['article']['permission_profiles'] = sportal_get_profiles(null, 1, 'name'); |
||
| 814 | if (empty($context['article']['permission_profiles'])) |
||
| 815 | { |
||
| 816 | throw new Elk_Exception('error_sp_no_permission_profiles', false); |
||
| 817 | } |
||
| 818 | |||
| 819 | $context['article']['style_profiles'] = sportal_get_profiles(null, 2, 'name'); |
||
| 820 | if (empty($context['article']['permission_profiles'])) |
||
| 821 | { |
||
| 822 | throw new Elk_Exception('error_sp_no_style_profiles', false); |
||
| 823 | } |
||
| 824 | |||
| 825 | $context['article']['categories'] = sportal_get_categories(); |
||
| 826 | if (empty($context['article']['categories'])) |
||
| 827 | { |
||
| 828 | throw new Elk_Exception('error_sp_no_category', false); |
||
| 829 | } |
||
| 830 | } |
||
| 831 | |||
| 832 | /** |
||
| 833 | * Handles the checking of uploaded attachments and loads valid ones |
||
| 834 | * into context |
||
| 835 | */ |
||
| 836 | private function article_attachment() |
||
| 837 | { |
||
| 838 | global $context, $txt; |
||
| 839 | |||
| 840 | // If there are attachments, calculate the total size and how many. |
||
| 841 | $this->_attachments = array(); |
||
| 842 | $this->_attachments['total_size'] = 0; |
||
| 843 | $this->_attachments['quantity'] = 0; |
||
| 844 | |||
| 845 | // If this isn't a new article, account for any current attachments. |
||
| 846 | if ($this->_is_aid && !empty($context['attachments'])) |
||
| 847 | { |
||
| 848 | $this->_attachments['quantity'] = count($context['attachments']['current']); |
||
| 849 | foreach ($context['attachments']['current'] as $attachment) |
||
| 850 | { |
||
| 851 | $this->_attachments['total_size'] += $attachment['size']; |
||
| 852 | } |
||
| 853 | } |
||
| 854 | |||
| 855 | // Any failed/aborted attachments left in session that we should clear |
||
| 856 | if (!empty($_SESSION['temp_attachments']) && empty($_POST['preview']) && empty($_POST['submit']) && !$this->_is_aid) |
||
| 857 | { |
||
| 858 | foreach ($_SESSION['temp_attachments'] as $attachID => $attachment) |
||
| 859 | { |
||
| 860 | unset($_SESSION['temp_attachments'][$attachID]); |
||
| 861 | @unlink($attachment['tmp_name']); |
||
| 862 | } |
||
| 863 | } |
||
| 864 | // New attachments to add |
||
| 865 | elseif (!empty($_SESSION['temp_attachments'])) |
||
| 866 | { |
||
| 867 | foreach ($_SESSION['temp_attachments'] as $attachID => $attachment) |
||
| 868 | { |
||
| 869 | // PHP upload error means we drop the file |
||
| 870 | if ($attachID === 'initial_error') |
||
| 871 | { |
||
| 872 | $txt['error_attach_initial_error'] = $txt['attach_no_upload'] . '<div class="attachmenterrors">' . (is_array($attachment) ? vsprintf($txt[$attachment[0]], $attachment[1]) : $txt[$attachment]) . '</div>'; |
||
| 873 | $this->attach_errors->addError('attach_initial_error'); |
||
| 874 | unset($_SESSION['temp_attachments']); |
||
| 875 | break; |
||
| 876 | } |
||
| 877 | |||
| 878 | // Show any errors which might have occurred. |
||
| 879 | if (!empty($attachment['errors'])) |
||
| 880 | { |
||
| 881 | $txt['error_attach_errors'] = empty($txt['error_attach_errors']) ? '<br />' : ''; |
||
| 882 | $txt['error_attach_errors'] .= vsprintf($txt['attach_warning'], $attachment['name']) . '<div class="attachmenterrors">'; |
||
| 883 | foreach ($attachment['errors'] as $error) |
||
| 884 | { |
||
| 885 | $txt['error_attach_errors'] .= (is_array($error) ? vsprintf($txt[$error[0]], $error[1]) : $txt[$error]) . '<br />'; |
||
| 886 | } |
||
| 887 | $txt['error_attach_errors'] .= '</div>'; |
||
| 888 | |||
| 889 | $this->attach_errors->addError('attach_errors'); |
||
| 890 | |||
| 891 | // Take out the trash. |
||
| 892 | unset($_SESSION['temp_attachments'][$attachID]); |
||
| 893 | @unlink($attachment['tmp_name']); |
||
| 894 | |||
| 895 | continue; |
||
| 896 | } |
||
| 897 | |||
| 898 | // In session but the file is missing, then some house cleaning |
||
| 899 | if (isset($attachment['tmp_name']) && !file_exists($attachment['tmp_name'])) |
||
| 900 | { |
||
| 901 | unset($_SESSION['temp_attachments'][$attachID]); |
||
| 902 | continue; |
||
| 903 | } |
||
| 904 | |||
| 905 | $this->_attachments['name'] = !empty($this->_attachments['name']) ? $this->_attachments['name'] : ''; |
||
| 906 | $this->_attachments['size'] = !empty($this->_attachments['size']) ? $this->_attachments['size'] : 0; |
||
| 907 | $this->_attachments['quantity']++; |
||
| 908 | $this->_attachments['total_size'] += $this->_attachments['size']; |
||
| 909 | |||
| 910 | $context['attachments']['current'][] = array( |
||
| 911 | 'name' => '<u>' . htmlspecialchars($this->_attachments['name'], ENT_COMPAT) . '</u>', |
||
| 912 | 'size' => $this->_attachments['size'], |
||
| 913 | 'id' => $attachID, |
||
| 914 | 'unchecked' => false, |
||
| 915 | 'approved' => 1, |
||
| 916 | ); |
||
| 917 | } |
||
| 918 | } |
||
| 919 | } |
||
| 920 | |||
| 921 | /** |
||
| 922 | * Prepares template values for the attachment area such as space left, |
||
| 923 | * types allowed, etc. |
||
| 924 | * |
||
| 925 | * Prepares the D&D JS initialization values |
||
| 926 | */ |
||
| 927 | private function article_attachment_dd() |
||
| 928 | { |
||
| 929 | global $context, $modSettings, $txt; |
||
| 930 | |||
| 931 | // If they've unchecked an attachment, they may still want to attach that many more files, but don't allow more than num_allowed_attachments. |
||
| 932 | $context['attachments']['num_allowed'] = empty($modSettings['attachmentNumPerPostLimit']) ? 50 : min($modSettings['attachmentNumPerPostLimit'] - count($context['attachments']['current']), $modSettings['attachmentNumPerPostLimit']); |
||
| 933 | $context['attachments']['can']['post_unapproved'] = allowedTo('post_attachment'); |
||
| 934 | $context['attachments']['restrictions'] = array(); |
||
| 935 | $context['attachments']['ila_enabled'] = !empty($modSettings['attachment_inline_enabled']); |
||
| 936 | |||
| 937 | if (!empty($modSettings['attachmentCheckExtensions'])) |
||
| 938 | { |
||
| 939 | $context['attachments']['allowed_extensions'] = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', ')); |
||
| 940 | } |
||
| 941 | else |
||
| 942 | { |
||
| 943 | $context['attachments']['allowed_extensions'] = ''; |
||
| 944 | } |
||
| 945 | |||
| 946 | $context['attachments']['templates'] = array( |
||
| 947 | 'existing' => 'template_article_existing_attachments', |
||
| 948 | 'add_new' => 'template_article_new_attachments', |
||
| 949 | ); |
||
| 950 | |||
| 951 | $attachmentRestrictionTypes = array('attachmentNumPerPostLimit', 'attachmentPostLimit', 'attachmentSizeLimit'); |
||
| 952 | foreach ($attachmentRestrictionTypes as $type) |
||
| 953 | { |
||
| 954 | if (!empty($modSettings[$type])) |
||
| 955 | { |
||
| 956 | $context['attachments']['restrictions'][] = sprintf($txt['attach_restrict_' . $type], comma_format($modSettings[$type], 0)); |
||
| 957 | |||
| 958 | // Show some numbers. If they exist. |
||
| 959 | if ($type === 'attachmentNumPerPostLimit' && $this->_attachments['quantity'] > 0) |
||
| 960 | { |
||
| 961 | $context['attachments']['restrictions'][] = sprintf($txt['attach_remaining'], $modSettings['attachmentNumPerPostLimit'] - $this->_attachments['quantity']); |
||
| 962 | } |
||
| 963 | elseif ($type === 'attachmentPostLimit' && $this->_attachments['total_size'] > 0) |
||
| 964 | { |
||
| 965 | $context['attachments']['restrictions'][] = sprintf($txt['attach_available'], comma_format(round(max($modSettings['attachmentPostLimit'] - ($this->_attachments['total_size'] / 1028), 0)), 0)); |
||
| 966 | } |
||
| 967 | } |
||
| 968 | } |
||
| 969 | |||
| 970 | // The portal articles always allow "ila" style attachments, so show that insert interface |
||
| 971 | addInlineJavascript(' |
||
| 972 | let IlaDropEvents = { |
||
| 973 | UploadSuccess: function($button, data) { |
||
| 974 | let inlineAttach = ElkInlineAttachments(\'#postAttachment2,#postAttachment\', \'' . $context['post_box_name'] . '\', { |
||
| 975 | trigger: $(\'<div class="share icon i-share" />\'), |
||
| 976 | template: ' . JavaScriptEscape('<div class="insertoverlay"> |
||
| 977 | <input type="button" class="button" value="' . $txt['insert'] . '"> |
||
| 978 | <ul data-group="tabs" class="tabs"> |
||
| 979 | <li data-tab="size">' . $txt['ila_opt_size'] . '</li><li data-tab="align">' . $txt['ila_opt_align'] . '</li> |
||
| 980 | </ul> |
||
| 981 | <div class="container" data-visual="size"> |
||
| 982 | <label><input data-size="thumb" type="radio" name="imgmode">' . $txt['ila_opt_size_thumb'] . '</label> |
||
| 983 | <label><input data-size="full" type="radio" name="imgmode">' . $txt['ila_opt_size_full'] . '</label> |
||
| 984 | <label><input data-size="cust" type="radio" name="imgmode">' . $txt['ila_opt_size_cust'] . '</label> |
||
| 985 | <div class="customsize"> |
||
| 986 | <input type="range" class="range" min="100" max="500"><input type="text" class="visualizesize" disabled="disabled"> |
||
| 987 | </div> |
||
| 988 | </div> |
||
| 989 | <div class="container" data-visual="align"> |
||
| 990 | <label><input data-align="none" type="radio" name="align">' . $txt['ila_opt_align_none'] . '</label> |
||
| 991 | <label><input data-align="left" type="radio" name="align">' . $txt['ila_opt_align_left'] . '</label> |
||
| 992 | <label><input data-align="center" type="radio" name="align">' . $txt['ila_opt_align_center'] . '</label> |
||
| 993 | <label><input data-align="right" type="radio" name="align">' . $txt['ila_opt_align_right'] . '</label> |
||
| 994 | </div> |
||
| 995 | </div>') . ' |
||
| 996 | }); |
||
| 997 | inlineAttach.addInterface($button, data.attachid); |
||
| 998 | }, |
||
| 999 | RemoveSuccess: function(attachid) { |
||
| 1000 | var inlineAttach = ElkInlineAttachments(\'#postAttachment2,#postAttachment\', \'' . $context['post_box_name'] . '\', { |
||
| 1001 | trigger: $(\'<div class="share icon i-share" />\') |
||
| 1002 | }); |
||
| 1003 | inlineAttach.removeAttach(attachid); |
||
| 1004 | } |
||
| 1005 | };', true); |
||
| 1006 | |||
| 1007 | // Load up the drag and drop attachment magic |
||
| 1008 | addInlineJavascript(' |
||
| 1009 | var dropAttach = dragDropAttachment({ |
||
| 1010 | board: 0, |
||
| 1011 | allowedExtensions: ' . JavaScriptEscape($context['attachments']['allowed_extensions']) . ', |
||
| 1012 | totalSizeAllowed: ' . JavaScriptEscape(empty($modSettings['attachmentPostLimit']) ? '' : $modSettings['attachmentPostLimit']) . ', |
||
| 1013 | individualSizeAllowed: ' . JavaScriptEscape(empty($modSettings['attachmentSizeLimit']) ? '' : $modSettings['attachmentSizeLimit']) . ', |
||
| 1014 | numOfAttachmentAllowed: ' . $context['attachments']['num_allowed'] . ', |
||
| 1015 | totalAttachSizeUploaded: ' . (isset($context['attachments']['total_size']) && !empty($context['attachments']['total_size']) ? $context['attachments']['total_size'] : 0) . ', |
||
| 1016 | numAttachUploaded: ' . (isset($context['attachments']['quantity']) && !empty($context['attachments']['quantity']) ? $context['attachments']['quantity'] : 0) . ', |
||
| 1017 | fileDisplayTemplate: \'<div class="statusbar"><div class="info"></div><div class="progressBar"><div></div></div><div class="control icon i-close"></div></div>\', |
||
| 1018 | oTxt: ({ |
||
| 1019 | allowedExtensions : ' . JavaScriptEscape(sprintf($txt['cant_upload_type'], $context['attachments']['allowed_extensions'])) . ', |
||
| 1020 | totalSizeAllowed : ' . JavaScriptEscape($txt['attach_max_total_file_size']) . ', |
||
| 1021 | individualSizeAllowed : ' . JavaScriptEscape(sprintf($txt['file_too_big'], comma_format($modSettings['attachmentSizeLimit'], 0))) . ', |
||
| 1022 | numOfAttachmentAllowed : ' . JavaScriptEscape(sprintf($txt['attachments_limit_per_post'], $modSettings['attachmentNumPerPostLimit'])) . ', |
||
| 1023 | postUploadError : ' . JavaScriptEscape($txt['post_upload_error']) . ', |
||
| 1024 | areYouSure: ' . JavaScriptEscape($txt['ila_confirm_removal']) . ', |
||
| 1025 | }), |
||
| 1026 | existingSelector: ".inline_insert", |
||
| 1027 | events: IlaDropEvents' . (isset($this->_is_aid) ? ', |
||
| 1028 | topic: ' . $this->_is_aid : '') . ' |
||
| 1029 | });', true); |
||
| 1030 | } |
||
| 1031 | |||
| 1032 | /** |
||
| 1033 | * Toggle an articles active status on/off |
||
| 1034 | */ |
||
| 1035 | public function action_status() |
||
| 1036 | { |
||
| 1037 | global $context; |
||
| 1038 | |||
| 1039 | checkSession(isset($_REQUEST['xml']) ? '' : 'get'); |
||
| 1040 | |||
| 1041 | $article_id = !empty($_REQUEST['article_id']) ? (int) $_REQUEST['article_id'] : 0; |
||
| 1042 | $state = sp_changeState('article', $article_id); |
||
| 1043 | |||
| 1044 | // Doing this the ajax way? |
||
| 1045 | if (isset($_REQUEST['xml'])) |
||
| 1046 | { |
||
| 1047 | $context['item_id'] = $article_id; |
||
| 1048 | $context['status'] = !empty($state) ? 'active' : 'deactive'; |
||
| 1049 | |||
| 1050 | // Clear out any template layers, add the xml response |
||
| 1051 | loadTemplate('PortalAdmin'); |
||
| 1052 | $template_layers = Template_Layers::instance(); |
||
| 1053 | $template_layers->removeAll(); |
||
| 1054 | $context['sub_template'] = 'change_status'; |
||
| 1055 | |||
| 1056 | obExit(); |
||
| 1057 | } |
||
| 1058 | |||
| 1059 | redirectexit('action=admin;area=portalarticles'); |
||
| 1060 | } |
||
| 1061 | |||
| 1062 | /** |
||
| 1063 | * Remove an article from the system |
||
| 1064 | * |
||
| 1065 | * - Removes the article |
||
| 1066 | * - Removes attachments associated with an article |
||
| 1067 | * - Updates category totals to reflect removed items |
||
| 1068 | */ |
||
| 1069 | public function action_delete() |
||
| 1103 | } |
||
| 1104 | } |
||
| 1105 |