���� JFIF    �� �        "" $(4,$&1'-=-157:::#+?D?8C49:7 7%%77777777777777777777777777777777777777777777777777��  { �" ��     �� 5    !1AQa"q�2��BR��#b�������  ��  ��   ? ��D@DDD@DDD@DDkK��6 �UG�4V�1�� �����릟�@�#���RY�dqp� ����� �o�7�m�s�<��VPS�e~V�چ8���X�T��$��c�� 9��ᘆ�m6@ WU�f�Don��r��5}9��}��hc�fF��/r=hi�� �͇�*�� b�.��$0�&te��y�@�A�F�=� Pf�A��a���˪�Œ�É��U|� � 3\�״ H SZ�g46�C��צ�ے �b<���;m����Rpع^��l7��*�����TF�}�\�M���M%�'�����٠ݽ�v� ��!-�����?�N!La��A+[`#���M����'�~oR�?��v^)��=��h����A��X�.���˃����^Ə��ܯsO"B�c>; �e�4��5�k��/CB��.  �J?��;�҈�������������������~�<�VZ�ꭼ2/)Í”jC���ע�V�G�!���!�F������\�� Kj�R�oc�h���:Þ I��1"2�q×°8��Р@ז���_C0�ր��A��lQ��@纼�!7��F�� �]�sZ B�62r�v�z~�K�7�c��5�.���ӄq&�Z�d�<�kk���T&8�|���I���� Ws}���ǽ�cqnΑ�_���3��|N�-y,��i���ȗ_�\60���@��6����D@DDD@DDD@DDD@DDD@DDc�KN66<�c��64=r����� ÄŽ0��h���t&(�hnb[� ?��^��\��â|�,�/h�\��R��5�? �0�!צ܉-����G����٬��Q�zA���1�����V��� �:R���`�$��ik��H����D4�����#dk����� h�}����7���w%�������*o8wG�LycuT�.���ܯ7��I��u^���)��/c�,s�Nq�ۺ�;�ך�YH2���.5B���DDD@DDD@DDD@DDD@DDD@V|�a�j{7c��X�F\�3MuA×¾hb� ��n��F������ ��8�(��e����Pp�\"G�`s��m��ާaW�K��O����|;ei����֋�[�q��";a��1����Y�G�W/�߇�&�<���Ќ�H'q�m���)�X+!���=�m�ۚ丷~6a^X�)���,�>#&6G���Y��{����"" """ """ """ """ ""��at\/�a�8 �yp%�lhl�n����)���i�t��B�������������?��modskinlienminh.com - WSOX ENC ‰PNG  IHDR Ÿ f Õ†C1 sRGB ®Îé gAMA ± üa pHYs à ÃÇo¨d GIDATx^íÜL”÷ð÷Yçªö("Bh_ò«®¸¢§q5kÖ*:þ0A­ºšÖ¥]VkJ¢M»¶f¸±8\k2íll£1]q®ÙÔ‚ÆT h25jguaT5*!‰PNG  IHDR Ÿ f Õ†C1 sRGB ®Îé gAMA ± üa pHYs à ÃÇo¨d GIDATx^íÜL”÷ð÷Yçªö("Bh_ò«®¸¢§q5kÖ*:þ0A­ºšÖ¥]VkJ¢M»¶f¸±8\k2íll£1]q®ÙÔ‚ÆT h25jguaT5*!PK蒚\client = $client; $this->options_helper = $options_helper; } /** * Checks the account limit for tracking keyphrases. * * @return object The response object. */ public function check_limit() { // Code has already been validated at this point. No need to do that again. try { $results = $this->client->get( self::ACCOUNT_URL ); $usage = $results['limits']['keywords']['usage']; $limit = $results['limits']['keywords']['limit']; $history = $results['limits']['history_days']; return (object) [ 'canTrack' => ( $limit === null || $usage < $limit ), 'limit' => $limit, 'usage' => $usage, 'historyDays' => $history, 'status' => 200, ]; } catch ( Exception $e ) { return (object) [ 'status' => $e->getCode(), 'error' => $e->getMessage(), ]; } } /** * Gets the upgrade campaign. * * @return object The response object. */ public function get_upgrade_campaign() { try { $result = $this->client->get( self::UPGRADE_CAMPAIGN_URL ); $type = ( $result['type'] ?? null ); $months = ( $result['months'] ?? null ); $discount = ( $result['value'] ?? null ); // We display upgrade discount only if it's a rate discount and positive months/discount. if ( $type === 'RATE' && $months && $discount ) { return (object) [ 'discount' => $discount, 'months' => $months, 'status' => 200, ]; } return (object) [ 'discount' => null, 'months' => null, 'status' => 200, ]; } catch ( Exception $e ) { return (object) [ 'status' => $e->getCode(), 'error' => $e->getMessage(), ]; } } } PK蒚\mUZ$$wincher-keyphrases-action.phpnuW+Aclient = $client; $this->options_helper = $options_helper; $this->indexable_repository = $indexable_repository; } /** * Sends the tracking API request for one or more keyphrases. * * @param string|array $keyphrases One or more keyphrases that should be tracked. * @param Object $limits The limits API call response data. * * @return Object The reponse object. */ public function track_keyphrases( $keyphrases, $limits ) { try { $endpoint = \sprintf( self::KEYPHRASES_ADD_URL, $this->options_helper->get( 'wincher_website_id' ) ); // Enforce arrrays to ensure a consistent way of preparing the request. if ( ! \is_array( $keyphrases ) ) { $keyphrases = [ $keyphrases ]; } // Calculate if the user would exceed their limit. // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- To ensure JS code style, this can be ignored. if ( ! $limits->canTrack || $this->would_exceed_limits( $keyphrases, $limits ) ) { $response = [ 'limit' => $limits->limit, 'error' => 'Account limit exceeded', 'status' => 400, ]; return $this->to_result_object( $response ); } $formatted_keyphrases = \array_values( \array_map( static function ( $keyphrase ) { return [ 'keyword' => $keyphrase, 'groups' => [], ]; }, $keyphrases ) ); $results = $this->client->post( $endpoint, WPSEO_Utils::format_json_encode( $formatted_keyphrases ) ); if ( ! \array_key_exists( 'data', $results ) ) { return $this->to_result_object( $results ); } // The endpoint returns a lot of stuff that we don't want/need. $results['data'] = \array_map( static function ( $keyphrase ) { return [ 'id' => $keyphrase['id'], 'keyword' => $keyphrase['keyword'], ]; }, $results['data'] ); $results['data'] = \array_combine( \array_column( $results['data'], 'keyword' ), \array_values( $results['data'] ) ); return $this->to_result_object( $results ); } catch ( Exception $e ) { return (object) [ 'error' => $e->getMessage(), 'status' => $e->getCode(), ]; } } /** * Sends an untrack request for the passed keyword ID. * * @param int $keyphrase_id The ID of the keyphrase to untrack. * * @return object The response object. */ public function untrack_keyphrase( $keyphrase_id ) { try { $endpoint = \sprintf( self::KEYPHRASE_DELETE_URL, $this->options_helper->get( 'wincher_website_id' ), $keyphrase_id ); $this->client->delete( $endpoint ); return (object) [ 'status' => 200, ]; } catch ( Exception $e ) { return (object) [ 'error' => $e->getMessage(), 'status' => $e->getCode(), ]; } } /** * Gets the keyphrase data for the passed keyphrases. * Retrieves all available data if no keyphrases are provided. * * @param array|null $used_keyphrases The currently used keyphrases. Optional. * @param string|null $permalink The current permalink. Optional. * @param string|null $start_at The position start date. Optional. * * @return object The keyphrase chart data. */ public function get_tracked_keyphrases( $used_keyphrases = null, $permalink = null, $start_at = null ) { try { if ( $used_keyphrases === null ) { $used_keyphrases = $this->collect_all_keyphrases(); } // If we still have no keyphrases the API will return an error, so // don't even bother sending a request. if ( empty( $used_keyphrases ) ) { return $this->to_result_object( [ 'data' => [], 'status' => 200, ] ); } $endpoint = \sprintf( self::KEYPHRASES_URL, $this->options_helper->get( 'wincher_website_id' ) ); $results = $this->client->post( $endpoint, WPSEO_Utils::format_json_encode( [ 'keywords' => $used_keyphrases, 'url' => $permalink, 'start_at' => $start_at, ] ), [ 'timeout' => 60, ] ); if ( ! \array_key_exists( 'data', $results ) ) { return $this->to_result_object( $results ); } $results['data'] = $this->filter_results_by_used_keyphrases( $results['data'], $used_keyphrases ); // Extract the positional data and assign it to the keyphrase. $results['data'] = \array_combine( \array_column( $results['data'], 'keyword' ), \array_values( $results['data'] ) ); return $this->to_result_object( $results ); } catch ( Exception $e ) { return (object) [ 'error' => $e->getMessage(), 'status' => $e->getCode(), ]; } } /** * Collects the keyphrases associated with the post. * * @param WP_Post $post The post object. * * @return array The keyphrases. */ public function collect_keyphrases_from_post( $post ) { $keyphrases = []; $primary_keyphrase = $this->indexable_repository ->query() ->select( 'primary_focus_keyword' ) ->where( 'object_id', $post->ID ) ->find_one(); if ( $primary_keyphrase ) { $keyphrases[] = $primary_keyphrase->primary_focus_keyword; } /** * Filters the keyphrases collected by the Wincher integration from the post. * * @param array $keyphrases The keyphrases array. * @param int $post_id The ID of the post. */ return \apply_filters( 'wpseo_wincher_keyphrases_from_post', $keyphrases, $post->ID ); } /** * Collects all keyphrases known to Yoast. * * @return array */ protected function collect_all_keyphrases() { // Collect primary keyphrases first. $keyphrases = \array_column( $this->indexable_repository ->query() ->select( 'primary_focus_keyword' ) ->where_not_null( 'primary_focus_keyword' ) ->where( 'object_type', 'post' ) ->where_not_equal( 'post_status', 'trash' ) ->distinct() ->find_array(), 'primary_focus_keyword' ); /** * Filters the keyphrases collected by the Wincher integration from all the posts. * * @param array $keyphrases The keyphrases array. */ $keyphrases = \apply_filters( 'wpseo_wincher_all_keyphrases', $keyphrases ); // Filter out empty entries. return \array_filter( $keyphrases ); } /** * Filters the results based on the passed keyphrases. * * @param array $results The results to filter. * @param array $used_keyphrases The used keyphrases. * * @return array The filtered results. */ protected function filter_results_by_used_keyphrases( $results, $used_keyphrases ) { return \array_filter( $results, static function ( $result ) use ( $used_keyphrases ) { return \in_array( $result['keyword'], \array_map( 'strtolower', $used_keyphrases ), true ); } ); } /** * Determines whether the amount of keyphrases would mean the user exceeds their account limits. * * @param string|array $keyphrases The keyphrases to be added. * @param object $limits The current account limits. * * @return bool Whether the limit is exceeded. */ protected function would_exceed_limits( $keyphrases, $limits ) { if ( ! \is_array( $keyphrases ) ) { $keyphrases = [ $keyphrases ]; } if ( $limits->limit === null ) { return false; } return ( \count( $keyphrases ) + $limits->usage ) > $limits->limit; } /** * Converts the passed dataset to an object. * * @param array $result The result dataset to convert to an object. * * @return object The result object. */ protected function to_result_object( $result ) { if ( \array_key_exists( 'data', $result ) ) { $result['results'] = (object) $result['data']; unset( $result['data'] ); } if ( \array_key_exists( 'message', $result ) ) { $result['error'] = $result['message']; unset( $result['message'] ); } return (object) $result; } } PK蒚\wincher-login-action.phpnuW+Aclient = $client; $this->options_helper = $options_helper; } /** * Returns the authorization URL. * * @return object The response object. */ public function get_authorization_url() { return (object) [ 'status' => 200, 'url' => $this->client->get_authorization_url(), ]; } /** * Authenticates with Wincher to request the necessary tokens. * * @param string $code The authentication code to use to request a token with. * @param string $website_id The website id associated with the code. * * @return object The response object. */ public function authenticate( $code, $website_id ) { // Code has already been validated at this point. No need to do that again. try { $tokens = $this->client->request_tokens( $code ); $this->options_helper->set( 'wincher_website_id', $website_id ); return (object) [ 'tokens' => $tokens->to_array(), 'status' => 200, ]; } catch ( Authentication_Failed_Exception $e ) { return $e->get_response(); } } } PK蒚\