Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like contentBlueprintsPages 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 contentBlueprintsPages, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | class contentBlueprintsPages extends AdministrationPage |
||
14 | { |
||
15 | public $_errors = array(); |
||
16 | protected $_hilights = array(); |
||
17 | |||
18 | /** |
||
19 | * The Pages page has /action/id/flag/ context. |
||
20 | * eg. /edit/1/saved/ |
||
21 | * |
||
22 | * @param array $context |
||
23 | * @param array $parts |
||
24 | * @return array |
||
25 | */ |
||
26 | View Code Duplication | public function parseContext(array &$context, array $parts) |
|
39 | |||
40 | public function insertBreadcrumbsUsingPageIdentifier($page_id, $preserve_last = true) |
||
83 | |||
84 | public function __viewIndex() |
||
85 | { |
||
86 | $this->setPageType('table'); |
||
87 | $this->setTitle(__('%1$s – %2$s', array(__('Pages'), __('Symphony')))); |
||
88 | |||
89 | $nesting = Symphony::Configuration()->get('pages_table_nest_children', 'symphony') === 'yes'; |
||
90 | |||
91 | if ($nesting && isset($_GET['parent']) && is_numeric($_GET['parent'])) { |
||
92 | $parent = PageManager::fetchPageByID((int)$_GET['parent'], array('title', 'id')); |
||
93 | } |
||
94 | |||
95 | $this->appendSubheading(isset($parent) ? $parent['title'] : __('Pages'), Widget::Anchor( |
||
96 | __('Create New'), Administration::instance()->getCurrentPageURL() . 'new/' . ($nesting && isset($parent) ? "?parent={$parent['id']}" : null), |
||
97 | __('Create a new page'), 'create button', null, array('accesskey' => 'c') |
||
98 | )); |
||
99 | |||
100 | if (isset($parent)) { |
||
101 | $this->insertBreadcrumbsUsingPageIdentifier($parent['id'], false); |
||
102 | } |
||
103 | |||
104 | $aTableHead = array( |
||
105 | array(__('Name'), 'col'), |
||
106 | array(__('Template'), 'col'), |
||
107 | array('<abbr title="' . __('Universal Resource Locator') . '">' . __('URL') . '</abbr>', 'col'), |
||
108 | array(__('Parameters'), 'col'), |
||
109 | array(__('Type'), 'col') |
||
110 | ); |
||
111 | $aTableBody = array(); |
||
112 | |||
113 | if ($nesting) { |
||
114 | $aTableHead[] = array(__('Children'), 'col'); |
||
115 | $where = array( |
||
116 | 'parent ' . (isset($parent) ? " = {$parent['id']} " : ' IS NULL ') |
||
117 | ); |
||
118 | } else { |
||
119 | $where = array(); |
||
120 | } |
||
121 | |||
122 | $pages = PageManager::fetch(true, array('*'), $where); |
||
123 | |||
124 | if (!is_array($pages) || empty($pages)) { |
||
125 | $aTableBody = array(Widget::TableRow(array( |
||
126 | Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead)) |
||
127 | ), 'odd')); |
||
128 | } else { |
||
129 | foreach ($pages as $page) { |
||
130 | $class = array(); |
||
131 | |||
132 | $page_title = ($nesting ? $page['title'] : PageManager::resolvePageTitle($page['id'])); |
||
133 | $page_url = URL . '/' . PageManager::resolvePagePath($page['id']) . '/'; |
||
134 | $page_edit_url = Administration::instance()->getCurrentPageURL() . 'edit/' . $page['id'] . '/'; |
||
135 | $page_template = PageManager::createFilePath($page['path'], $page['handle']); |
||
136 | |||
137 | $col_title = Widget::TableData(Widget::Anchor($page_title, $page_edit_url, $page['handle'])); |
||
138 | $col_title->appendChild(Widget::Label(__('Select Page %s', array($page_title)), null, 'accessible', null, array( |
||
139 | 'for' => 'page-' . $page['id'] |
||
140 | ))); |
||
141 | $col_title->appendChild(Widget::Input('items['.$page['id'].']', 'on', 'checkbox', array( |
||
142 | 'id' => 'page-' . $page['id'] |
||
143 | ))); |
||
144 | |||
145 | $col_template = Widget::TableData($page_template . '.xsl'); |
||
146 | |||
147 | $col_url = Widget::TableData(Widget::Anchor($page_url, $page_url)); |
||
148 | |||
149 | View Code Duplication | if ($page['params']) { |
|
150 | $col_params = Widget::TableData(trim($page['params'], '/')); |
||
151 | } else { |
||
152 | $col_params = Widget::TableData(__('None'), 'inactive'); |
||
153 | } |
||
154 | |||
155 | View Code Duplication | if (!empty($page['type'])) { |
|
156 | $col_types = Widget::TableData(implode(', ', $page['type'])); |
||
157 | } else { |
||
158 | $col_types = Widget::TableData(__('None'), 'inactive'); |
||
159 | } |
||
160 | |||
161 | if (in_array($page['id'], $this->_hilights)) { |
||
162 | $class[] = 'failed'; |
||
163 | } |
||
164 | |||
165 | $columns = array($col_title, $col_template, $col_url, $col_params, $col_types); |
||
166 | |||
167 | if ($nesting) { |
||
168 | if (PageManager::hasChildPages($page['id'])) { |
||
169 | $col_children = Widget::TableData( |
||
170 | Widget::Anchor(PageManager::getChildPagesCount($page['id']) . ' →', |
||
171 | SYMPHONY_URL . '/blueprints/pages/?parent=' . $page['id']) |
||
172 | ); |
||
173 | } else { |
||
174 | $col_children = Widget::TableData(__('None'), 'inactive'); |
||
175 | } |
||
176 | |||
177 | $columns[] = $col_children; |
||
178 | } |
||
179 | |||
180 | $aTableBody[] = Widget::TableRow( |
||
181 | $columns, |
||
182 | implode(' ', $class) |
||
183 | ); |
||
184 | } |
||
185 | } |
||
186 | |||
187 | $table = Widget::Table( |
||
188 | Widget::TableHead($aTableHead), null, |
||
189 | Widget::TableBody($aTableBody), 'orderable selectable', |
||
190 | null, array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading', 'data-interactive' => 'data-interactive') |
||
191 | ); |
||
192 | |||
193 | $this->Form->appendChild($table); |
||
194 | |||
195 | $version = new XMLElement('p', 'Symphony ' . Symphony::Configuration()->get('version', 'symphony'), array( |
||
196 | 'id' => 'version' |
||
197 | )); |
||
198 | $this->Form->appendChild($version); |
||
199 | |||
200 | $tableActions = new XMLElement('div'); |
||
201 | $tableActions->setAttribute('class', 'actions'); |
||
202 | |||
203 | $options = array( |
||
204 | array(null, false, __('With Selected...')), |
||
205 | array('delete', false, __('Delete'), 'confirm', null, array( |
||
206 | 'data-message' => __('Are you sure you want to delete the selected pages?') |
||
207 | )) |
||
208 | ); |
||
209 | |||
210 | /** |
||
211 | * Allows an extension to modify the existing options for this page's |
||
212 | * With Selected menu. If the `$options` parameter is an empty array, |
||
213 | * the 'With Selected' menu will not be rendered. |
||
214 | * |
||
215 | * @delegate AddCustomActions |
||
216 | * @since Symphony 2.3.2 |
||
217 | * @param string $context |
||
218 | * '/blueprints/pages/' |
||
219 | * @param array $options |
||
220 | * An array of arrays, where each child array represents an option |
||
221 | * in the With Selected menu. Options should follow the same format |
||
222 | * expected by `Widget::__SelectBuildOption`. Passed by reference. |
||
223 | */ |
||
224 | Symphony::ExtensionManager()->notifyMembers('AddCustomActions', '/blueprints/pages/', array( |
||
225 | 'options' => &$options |
||
226 | )); |
||
227 | |||
228 | if (!empty($options)) { |
||
229 | $tableActions->appendChild(Widget::Apply($options)); |
||
230 | $this->Form->appendChild($tableActions); |
||
231 | } |
||
232 | } |
||
233 | |||
234 | public function __viewNew() |
||
235 | { |
||
236 | $this->__viewEdit(); |
||
237 | } |
||
238 | |||
239 | public function __viewEdit() |
||
542 | |||
543 | public function __compare_pages($a, $b) |
||
544 | { |
||
545 | return strnatcasecmp($a[2], $b[2]); |
||
546 | } |
||
547 | |||
548 | public function __actionIndex() |
||
549 | { |
||
550 | $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null; |
||
551 | |||
552 | if (is_array($checked) && !empty($checked)) { |
||
553 | /** |
||
554 | * Extensions can listen for any custom actions that were added |
||
555 | * through `AddCustomPreferenceFieldsets` or `AddCustomActions` |
||
556 | * delegates. |
||
557 | * |
||
558 | * @delegate CustomActions |
||
559 | * @since Symphony 2.3.2 |
||
560 | * @param string $context |
||
561 | * '/blueprints/pages/' |
||
562 | * @param array $checked |
||
563 | * An array of the selected rows. The value is usually the ID of the |
||
564 | * the associated object. |
||
565 | */ |
||
566 | Symphony::ExtensionManager()->notifyMembers('CustomActions', '/blueprints/pages/', array( |
||
567 | 'checked' => $checked |
||
568 | )); |
||
569 | |||
570 | switch ($_POST['with-selected']) { |
||
571 | case 'delete': |
||
572 | $this->__actionDelete($checked, SYMPHONY_URL . '/blueprints/pages/'); |
||
573 | break; |
||
574 | } |
||
575 | } |
||
576 | } |
||
577 | |||
578 | public function __actionNew() |
||
582 | |||
583 | public function __actionEdit() |
||
584 | { |
||
585 | if ($this->_context['action'] !== 'new' && !$page_id = (int)$this->_context['id']) { |
||
586 | redirect(SYMPHONY_URL . '/blueprints/pages/'); |
||
587 | } |
||
588 | |||
589 | $parent_link_suffix = null; |
||
590 | |||
591 | View Code Duplication | if (isset($_REQUEST['parent']) && is_numeric($_REQUEST['parent'])) { |
|
592 | $parent_link_suffix = '?parent=' . $_REQUEST['parent']; |
||
593 | } |
||
594 | |||
595 | if (@array_key_exists('delete', $_POST['action'])) { |
||
596 | $this->__actionDelete($page_id, SYMPHONY_URL . '/blueprints/pages/' . $parent_link_suffix); |
||
597 | } |
||
598 | |||
599 | if (@array_key_exists('save', $_POST['action'])) { |
||
600 | $fields = $_POST['fields']; |
||
601 | $this->_errors = array(); |
||
602 | $autogenerated_handle = false; |
||
603 | |||
604 | View Code Duplication | if (!isset($fields['title']) || trim($fields['title']) === '') { |
|
605 | $this->_errors['title'] = __('This is a required field'); |
||
606 | } |
||
607 | |||
608 | if (trim($fields['type']) !== '' && preg_match('/(index|404|403)/i', $fields['type'])) { |
||
609 | $types = preg_split('/\s*,\s*/', strtolower($fields['type']), -1, PREG_SPLIT_NO_EMPTY); |
||
610 | |||
611 | if (in_array('index', $types) && PageManager::hasPageTypeBeenUsed($page_id, 'index')) { |
||
612 | $this->_errors['type'] = __('An index type page already exists.'); |
||
613 | } elseif (in_array('404', $types) && PageManager::hasPageTypeBeenUsed($page_id, '404')) { |
||
614 | $this->_errors['type'] = __('A 404 type page already exists.'); |
||
615 | } elseif (in_array('403', $types) && PageManager::hasPageTypeBeenUsed($page_id, '403')) { |
||
616 | $this->_errors['type'] = __('A 403 type page already exists.'); |
||
617 | } |
||
618 | } |
||
619 | |||
620 | if (trim($fields['handle']) === '') { |
||
621 | $fields['handle'] = $fields['title']; |
||
622 | $autogenerated_handle = true; |
||
623 | } |
||
624 | |||
625 | $fields['handle'] = PageManager::createHandle($fields['handle']); |
||
626 | |||
627 | if (empty($fields['handle']) && !isset($this->_errors['title'])) { |
||
628 | $this->_errors['handle'] = __('Please ensure handle contains at least one Latin-based character.'); |
||
629 | } |
||
630 | |||
631 | /** |
||
632 | * Just after the Symphony validation has run, allows Developers |
||
633 | * to run custom validation logic on a Page |
||
634 | * |
||
635 | * @delegate PagePostValidate |
||
636 | * @since Symphony 2.2 |
||
637 | * @param string $context |
||
638 | * '/blueprints/pages/' |
||
639 | * @param array $fields |
||
640 | * The `$_POST['fields']` array. This should be read-only and not changed |
||
641 | * through this delegate. |
||
642 | * @param array $errors |
||
643 | * An associative array of errors, with the key matching a key in the |
||
644 | * `$fields` array, and the value being the string of the error. `$errors` |
||
645 | * is passed by reference. |
||
646 | */ |
||
647 | Symphony::ExtensionManager()->notifyMembers('PagePostValidate', '/blueprints/pages/', array('fields' => $fields, 'errors' => &$errors)); |
||
648 | |||
649 | if (empty($this->_errors)) { |
||
650 | $autogenerated_handle = false; |
||
651 | |||
652 | if ($fields['params']) { |
||
653 | $fields['params'] = trim(preg_replace('@\/{2,}@', '/', $fields['params']), '/'); |
||
654 | } |
||
655 | |||
656 | // Clean up type list |
||
657 | $types = preg_split('/\s*,\s*/', $fields['type'], -1, PREG_SPLIT_NO_EMPTY); |
||
658 | $types = @array_map('trim', $types); |
||
659 | unset($fields['type']); |
||
660 | |||
661 | $fields = array_filter($fields); |
||
662 | |||
663 | $fields['parent'] = ($fields['parent'] !== __('None') ? $fields['parent'] : null); |
||
664 | $fields['data_sources'] = is_array($fields['data_sources']) ? implode(',', $fields['data_sources']) : null; |
||
665 | $fields['events'] = is_array($fields['events']) ? implode(',', $fields['events']) : null; |
||
666 | $fields['path'] = null; |
||
667 | |||
668 | if ($fields['parent']) { |
||
669 | $fields['path'] = PageManager::resolvePagePath((integer)$fields['parent']); |
||
670 | } |
||
671 | |||
672 | // Check for duplicates: |
||
673 | $current = PageManager::fetchPageByID($page_id); |
||
674 | |||
675 | if (empty($current)) { |
||
676 | $fields['sortorder'] = PageManager::fetchNextSortOrder(); |
||
677 | } |
||
678 | |||
679 | $where = array(); |
||
680 | |||
681 | if (!empty($current)) { |
||
682 | $where[] = "p.id != {$page_id}"; |
||
683 | } |
||
684 | |||
685 | $where[] = "p.handle = '" . $fields['handle'] . "'"; |
||
686 | $where[] = (is_null($fields['path'])) |
||
687 | ? "p.path IS null" |
||
688 | : "p.path = '" . $fields['path'] . "'"; |
||
689 | $duplicate = PageManager::fetch(false, array('*'), $where); |
||
690 | |||
691 | // If duplicate |
||
692 | if (!empty($duplicate)) { |
||
693 | if ($autogenerated_handle) { |
||
694 | $this->_errors['title'] = __('A page with that title already exists'); |
||
695 | } else { |
||
696 | $this->_errors['handle'] = __('A page with that handle already exists'); |
||
697 | } |
||
698 | |||
699 | // Create or move files: |
||
700 | } else { |
||
701 | // New page? |
||
702 | if (empty($current)) { |
||
703 | $file_created = PageManager::createPageFiles( |
||
704 | $fields['path'], |
||
705 | $fields['handle'] |
||
706 | ); |
||
707 | |||
708 | // Existing page, potentially rename files |
||
709 | } else { |
||
710 | $file_created = PageManager::createPageFiles( |
||
711 | $fields['path'], |
||
712 | $fields['handle'], |
||
713 | $current['path'], |
||
714 | $current['handle'] |
||
715 | ); |
||
716 | } |
||
717 | |||
718 | // If the file wasn't created, it's usually permissions related |
||
719 | if (!$file_created) { |
||
720 | $redirect = null; |
||
721 | return $this->pageAlert( |
||
722 | __('Page Template could not be written to disk.') |
||
723 | . ' ' . __('Please check permissions on %s.', array('<code>/workspace/pages</code>')), |
||
724 | Alert::ERROR |
||
725 | ); |
||
726 | } |
||
727 | |||
728 | // Insert the new data: |
||
729 | if (empty($current)) { |
||
730 | /** |
||
731 | * Just prior to creating a new Page record in `tbl_pages`, provided |
||
732 | * with the `$fields` associative array. Use with caution, as no |
||
733 | * duplicate page checks are run after this delegate has fired |
||
734 | * |
||
735 | * @delegate PagePreCreate |
||
736 | * @since Symphony 2.2 |
||
737 | * @param string $context |
||
738 | * '/blueprints/pages/' |
||
739 | * @param array $fields |
||
740 | * The `$_POST['fields']` array passed by reference |
||
741 | */ |
||
742 | Symphony::ExtensionManager()->notifyMembers('PagePreCreate', '/blueprints/pages/', array('fields' => &$fields)); |
||
743 | |||
744 | View Code Duplication | if (!$page_id = PageManager::add($fields)) { |
|
745 | $this->pageAlert( |
||
746 | __('Unknown errors occurred while attempting to save.') |
||
747 | . '<a href="' . SYMPHONY_URL . '/system/log/">' |
||
748 | . __('Check your activity log') |
||
749 | . '</a>.', |
||
750 | Alert::ERROR |
||
751 | ); |
||
752 | } else { |
||
753 | /** |
||
754 | * Just after the creation of a new page in `tbl_pages` |
||
755 | * |
||
756 | * @delegate PagePostCreate |
||
757 | * @since Symphony 2.2 |
||
758 | * @param string $context |
||
759 | * '/blueprints/pages/' |
||
760 | * @param integer $page_id |
||
761 | * The ID of the newly created Page |
||
762 | * @param array $fields |
||
763 | * An associative array of data that was just saved for this page |
||
764 | */ |
||
765 | Symphony::ExtensionManager()->notifyMembers('PagePostCreate', '/blueprints/pages/', array('page_id' => $page_id, 'fields' => &$fields)); |
||
766 | |||
767 | $redirect = "/blueprints/pages/edit/{$page_id}/created/{$parent_link_suffix}"; |
||
768 | } |
||
769 | |||
770 | // Update existing: |
||
771 | } else { |
||
772 | /** |
||
773 | * Just prior to updating a Page record in `tbl_pages`, provided |
||
774 | * with the `$fields` associative array. Use with caution, as no |
||
775 | * duplicate page checks are run after this delegate has fired |
||
776 | * |
||
777 | * @delegate PagePreEdit |
||
778 | * @since Symphony 2.2 |
||
779 | * @param string $context |
||
780 | * '/blueprints/pages/' |
||
781 | * @param integer $page_id |
||
782 | * The ID of the Page that is about to be updated |
||
783 | * @param array $fields |
||
784 | * The `$_POST['fields']` array passed by reference |
||
785 | */ |
||
786 | Symphony::ExtensionManager()->notifyMembers('PagePreEdit', '/blueprints/pages/', array('page_id' => $page_id, 'fields' => &$fields)); |
||
787 | |||
788 | View Code Duplication | if (!PageManager::edit($page_id, $fields, true)) { |
|
789 | return $this->pageAlert( |
||
790 | __('Unknown errors occurred while attempting to save.') |
||
791 | . '<a href="' . SYMPHONY_URL . '/system/log/">' |
||
792 | . __('Check your activity log') |
||
793 | . '</a>.', |
||
794 | Alert::ERROR |
||
795 | ); |
||
796 | } else { |
||
797 | /** |
||
798 | * Just after updating a page in `tbl_pages` |
||
799 | * |
||
800 | * @delegate PagePostEdit |
||
801 | * @since Symphony 2.2 |
||
802 | * @param string $context |
||
803 | * '/blueprints/pages/' |
||
804 | * @param integer $page_id |
||
805 | * The ID of the Page that was just updated |
||
806 | * @param array $fields |
||
807 | * An associative array of data that was just saved for this page |
||
808 | */ |
||
809 | Symphony::ExtensionManager()->notifyMembers('PagePostEdit', '/blueprints/pages/', array('page_id' => $page_id, 'fields' => $fields)); |
||
810 | |||
811 | $redirect = "/blueprints/pages/edit/{$page_id}/saved/{$parent_link_suffix}"; |
||
812 | } |
||
813 | } |
||
814 | } |
||
815 | |||
816 | // Only proceed if there was no errors saving/creating the page |
||
817 | if (empty($this->_errors)) { |
||
818 | /** |
||
819 | * Just before the page's types are saved into `tbl_pages_types`. |
||
820 | * Use with caution as no further processing is done on the `$types` |
||
821 | * array to prevent duplicate `$types` from occurring (ie. two index |
||
822 | * page types). Your logic can use the PageManger::hasPageTypeBeenUsed |
||
823 | * function to perform this logic. |
||
824 | * |
||
825 | * @delegate PageTypePreCreate |
||
826 | * @since Symphony 2.2 |
||
827 | * @see toolkit.PageManager#hasPageTypeBeenUsed |
||
828 | * @param string $context |
||
829 | * '/blueprints/pages/' |
||
830 | * @param integer $page_id |
||
831 | * The ID of the Page that was just created or updated |
||
832 | * @param array $types |
||
833 | * An associative array of the types for this page passed by reference. |
||
834 | */ |
||
835 | Symphony::ExtensionManager()->notifyMembers('PageTypePreCreate', '/blueprints/pages/', array('page_id' => $page_id, 'types' => &$types)); |
||
836 | |||
837 | // Assign page types: |
||
838 | PageManager::addPageTypesToPage($page_id, $types); |
||
839 | |||
840 | // Find and update children: |
||
841 | if ($this->_context['action'] === 'edit') { |
||
842 | PageManager::editPageChildren($page_id, $fields['path'] . '/' . $fields['handle']); |
||
843 | } |
||
844 | |||
845 | if ($redirect) { |
||
846 | redirect(SYMPHONY_URL . $redirect); |
||
847 | } |
||
848 | } |
||
849 | } |
||
850 | |||
851 | // If there was any errors, either with pre processing or because of a |
||
852 | // duplicate page, return. |
||
853 | View Code Duplication | if (is_array($this->_errors) && !empty($this->_errors)) { |
|
854 | return $this->pageAlert( |
||
855 | __('An error occurred while processing this form. See below for details.'), |
||
856 | Alert::ERROR |
||
857 | ); |
||
858 | } |
||
859 | } |
||
860 | } |
||
861 | |||
862 | public function __actionDelete($pages, $redirect) |
||
943 | } |
||
944 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.