| @@ -649,9 +649,9 @@ | ||
| 649 | 649 | } |
| 650 | 650 | $class_name = $id . $name; |
| 651 | 651 | $properties = implode( ' !important; ', array_filter( $attributes ) ); |
| 652 | 652 | if ( ! empty( $properties ) ) { |
| 653 | - $css .= '.' . $class_name . ' {' . $properties . ' !important;}'; | |
| 653 | + $css .= wp_strip_all_tags( '.' . $class_name . ' {' . $properties . ' !important;}' ); | |
| 654 | 654 | $classes[ $name ] = $class_name; |
| 655 | 655 | } |
| 656 | 656 | } |
| 657 | 657 | $settings['cssClassNames'] = $classes; |
| @@ -722,8 +722,29 @@ | ||
| 722 | 722 | return $q->found_posts; |
| 723 | 723 | } |
| 724 | 724 | |
| 725 | 725 | /** |
| 726 | + * Checks whether the current user may edit a specific chart. | |
| 727 | + * | |
| 728 | + * @param int $chart_id Chart ID. | |
| 729 | + * @return bool | |
| 730 | + */ | |
| 731 | + public static function can_edit_chart( $chart_id ) { | |
| 732 | + $chart_id = absint( $chart_id ); | |
| 733 | + if ( ! $chart_id ) { | |
| 734 | + return false; | |
| 735 | + } | |
| 736 | + | |
| 737 | + $chart = get_post( $chart_id ); | |
| 738 | + return $chart | |
| 739 | + && Visualizer_Plugin::CPT_VISUALIZER === $chart->post_type | |
| 740 | + && ( | |
| 741 | + current_user_can( 'edit_post', $chart_id ) | |
| 742 | + || ( (int) $chart->post_author === get_current_user_id() && current_user_can( 'edit_posts' ) ) | |
| 743 | + ); | |
| 744 | + } | |
| 745 | + | |
| 746 | + /** | |
| 726 | 747 | * Checks if the PRO version is active. |
| 727 | 748 | * |
| 728 | 749 | * @since 3.3.0 |
| 729 | 750 | */ |
| @@ -778,8 +799,87 @@ | ||
| 778 | 799 | } |
| 779 | 800 | } |
| 780 | 801 | |
| 781 | 802 | /** |
| 803 | + * Safely unserialize chart/source content, blocking PHP object injection. | |
| 804 | + * | |
| 805 | + * Single guarded chokepoint shared by chart/source content sinks so the | |
| 806 | + * allowed_classes guard cannot be dropped from one call site independently. | |
| 807 | + * | |
| 808 | + * @param mixed $content The serialized content (only strings are decoded). | |
| 809 | + * @return mixed The decoded value (array for valid chart data), or false. | |
| 810 | + */ | |
| 811 | + public static function decode_content( $content ) { | |
| 812 | + if ( ! is_string( $content ) ) { | |
| 813 | + return false; | |
| 814 | + } | |
| 815 | + $value = unserialize( trim( $content ), array( 'allowed_classes' => false ) ); | |
| 816 | + if ( self::contains_references( $value ) ) { | |
| 817 | + return false; | |
| 818 | + } | |
| 819 | + return self::strip_incomplete_objects( $value ); | |
| 820 | + } | |
| 821 | + | |
| 822 | + /** | |
| 823 | + * Check decoded arrays for references before recursively processing them. | |
| 824 | + * | |
| 825 | + * Cyclic serialized arrays necessarily contain a reference. Rejecting all | |
| 826 | + * references also prevents shared references from becoming cycles later, | |
| 827 | + * so strip_incomplete_objects() cannot recurse without terminating. | |
| 828 | + * | |
| 829 | + * @param mixed $value The decoded value. | |
| 830 | + * @return bool Whether the value contains an array reference. | |
| 831 | + */ | |
| 832 | + private static function contains_references( $value ) { | |
| 833 | + if ( ! is_array( $value ) ) { | |
| 834 | + return false; | |
| 835 | + } | |
| 836 | + foreach ( array_keys( $value ) as $key ) { | |
| 837 | + if ( null !== ReflectionReference::fromArrayElement( $value, $key ) ) { | |
| 838 | + return true; | |
| 839 | + } | |
| 840 | + if ( is_array( $value[ $key ] ) && self::contains_references( $value[ $key ] ) ) { | |
| 841 | + return true; | |
| 842 | + } | |
| 843 | + } | |
| 844 | + return false; | |
| 845 | + } | |
| 846 | + | |
| 847 | + /** | |
| 848 | + * Remove the __PHP_Incomplete_Class stubs the allowed_classes guard leaves | |
| 849 | + * behind; they crash map_deep() when the decoded value is written back to | |
| 850 | + * post meta. Legitimate chart content is nested arrays/scalars only. | |
| 851 | + * | |
| 852 | + * @param mixed $value The decoded value. | |
| 853 | + * @return mixed The value without object stubs; false for a top-level stub. | |
| 854 | + */ | |
| 855 | + private static function strip_incomplete_objects( $value ) { | |
| 856 | + if ( $value instanceof __PHP_Incomplete_Class ) { | |
| 857 | + return false; | |
| 858 | + } | |
| 859 | + if ( is_array( $value ) ) { | |
| 860 | + foreach ( $value as $key => $item ) { | |
| 861 | + if ( $item instanceof __PHP_Incomplete_Class ) { | |
| 862 | + unset( $value[ $key ] ); | |
| 863 | + } elseif ( is_array( $item ) ) { | |
| 864 | + $value[ $key ] = self::strip_incomplete_objects( $item ); | |
| 865 | + } | |
| 866 | + } | |
| 867 | + } | |
| 868 | + return $value; | |
| 869 | + } | |
| 870 | + | |
| 871 | + /** | |
| 872 | + * Object-injection-safe drop-in for maybe_unserialize(). | |
| 873 | + * | |
| 874 | + * @param mixed $value Raw meta/content value. | |
| 875 | + * @return mixed The decoded value for serialized input, the value unchanged otherwise. | |
| 876 | + */ | |
| 877 | + public static function maybe_decode_content( $value ) { | |
| 878 | + return is_serialized( $value ) ? self::decode_content( $value ) : $value; | |
| 879 | + } | |
| 880 | + | |
| 881 | + /** | |
| 782 | 882 | * Gets the chart content after common manipulations. |
| 783 | 883 | */ |
| 784 | 884 | public static function get_chart_data( $chart, $type, $run_filter = true ) { |
| 785 | 885 | // change HTML entities |
| @@ -792,9 +892,9 @@ | ||
| 792 | 892 | } |
| 793 | 893 | }, |
| 794 | 894 | $post_content |
| 795 | 895 | ); |
| 796 | - $data = unserialize( $post_content ); | |
| 896 | + $data = self::decode_content( $post_content ); | |
| 797 | 897 | $altered = array(); |
| 798 | 898 | if ( ! empty( $data ) ) { |
| 799 | 899 | foreach ( $data as $index => $array ) { |
| 800 | 900 | if ( ! is_array( $index ) && is_array( $array ) ) { |