212 | );
213 | }
214 |
215 | /**
216 | * Perform the update call to the REST API.
217 | *
218 | * @param {Object} data Data set containing updates to be sent to the REST API.
219 | */
220 | async update( data ) {
221 | const result = await this.api.update( data );
222 |
223 | this.addNotice(
224 | data.domain,
225 | result.code
226 | ? result.message
227 | : sprintf(
228 | /* translators: domain name */
229 | __( 'Successfully updated %s.', 'dark-matter' ),
230 | data.domain
231 | ),
232 | result.code ? 'error' : 'success'
233 | );
234 |
235 | this.getData();
236 | }
237 | }
238 |
239 | export default DomainMapping;
240 |
--------------------------------------------------------------------------------
/domain-mapping/api/class-darkmatter-primary.php:
--------------------------------------------------------------------------------
1 | dm_table = $wpdb->base_prefix . 'domain_mapping';
51 |
52 | /**
53 | * Store a reference to $wpdb as it will be used a lot.
54 | */
55 | $this->wpdb = $wpdb;
56 | }
57 |
58 | /**
59 | * Retrieve the Primary domain for a Site.
60 | *
61 | * @since 2.0.0
62 | *
63 | * @param integer $site_id Site ID to retrieve the primary domain for.
64 | * @return DM_Domain|boolean Returns the DM_Domain object on success. False otherwise.
65 | */
66 | public function get( $site_id = 0 ) {
67 | $site_id = ( empty( $site_id ) ? get_current_blog_id() : $site_id );
68 |
69 | /**
70 | * Attempt to retrieve the domain from cache.
71 | */
72 | $cache_key = $site_id . '-primary';
73 | $primary_domain = wp_cache_get( $cache_key, 'dark-matter' );
74 |
75 | /**
76 | * If the Cache is unavailable, then attempt to load the domain from the
77 | * database and re-prime the cache.
78 | */
79 | if ( ! $primary_domain ) {
80 | // phpcs:ignore
81 | $primary_domain = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT domain FROM {$this->dm_table} WHERE is_primary = 1 AND blog_id = %s", $site_id ) );
82 |
83 | if ( empty( $primary_domain ) ) {
84 | /**
85 | * Set the cached value for Primary Domain to "none". This will
86 | * stop spurious database queries for some thing that has not
87 | * been setup up.
88 | */
89 | wp_cache_set( $cache_key, 'none', 'dark-matter' );
90 |
91 | /**
92 | * As the cache is modified, we update the `last_changed`.
93 | */
94 | $this->update_last_changed();
95 |
96 | return false;
97 | }
98 | }
99 |
100 | /**
101 | * Return false if the cache value is "none".
102 | */
103 | if ( 'none' === $primary_domain ) {
104 | return false;
105 | }
106 |
107 | /**
108 | * Retrieve the entire Domain object.
109 | */
110 | $db = DarkMatter_Domains::instance();
111 | $_domain = $db->get( $primary_domain );
112 |
113 | return $_domain;
114 | }
115 |
116 | /**
117 | * Retrieve all primary domains for the Network.
118 | *
119 | * @since 2.0.0
120 | *
121 | * @return array Array of DM_Domain objects of the Primary domains for each Site in the Network.
122 | */
123 | public function get_all() {
124 | global $wpdb;
125 |
126 | // phpcs:ignore
127 | $_domains = $wpdb->get_col( "SELECT domain FROM {$this->dm_table} WHERE is_primary = 1 ORDER BY blog_id DESC, domain" );
128 |
129 | if ( empty( $_domains ) ) {
130 | return array();
131 | }
132 |
133 | $db = DarkMatter_Domains::instance();
134 |
135 | /**
136 | * Retrieve the DM_Domain objects for each of the primary domains.
137 | */
138 | $domains = array();
139 |
140 | foreach ( $_domains as $_domain ) {
141 | $domains[] = $db->get( $_domain );
142 | }
143 |
144 | return $domains;
145 | }
146 |
147 | /**
148 | * Helper function to the set the cache for the primary domain for a Site.
149 | *
150 | * @since 2.0.0
151 | *
152 | * @param integer $site_id Site ID to set the primary domain cache for.
153 | * @param string $domain Domain to be stored in the cache.
154 | * @return boolean True on success, false otherwise.
155 | */
156 | public function set( $site_id = 0, $domain = '' ) {
157 | $new_primary_domain = DarkMatter_Domains::instance()->get( $domain );
158 |
159 | if ( $new_primary_domain->blog_id !== $site_id ) {
160 | return false;
161 | }
162 |
163 | $result = DarkMatter_Domains::instance()->update(
164 | $new_primary_domain->domain,
165 | true,
166 | $new_primary_domain->is_https,
167 | true,
168 | $new_primary_domain->active,
169 | DM_DOMAIN_TYPE_MAIN
170 | );
171 |
172 | if ( is_wp_error( $result ) ) {
173 | return false;
174 | }
175 |
176 | return true;
177 | }
178 |
179 | /**
180 | * Unset the primary domain for a given Site. By default, will change all
181 | * records with is_primary set to true.
182 | *
183 | * @since 2.0.0
184 | *
185 | * @param integer $site_id Site ID to unset the primary domain for.
186 | * @param string $domain Optional. If provided, will only affect that domain's record.
187 | * @param boolean $db Set to true to perform a database update.
188 | * @return boolean True on success. False otherwise.
189 | */
190 | public function unset( $site_id = 0, $domain = '', $db = false ) {
191 | $new_primary_domain = DarkMatter_Domains::instance()->get( $domain );
192 |
193 | if ( $new_primary_domain->blog_id !== $site_id ) {
194 | return false;
195 | }
196 |
197 | $result = DarkMatter_Domains::instance()->update(
198 | $new_primary_domain->domain,
199 | false,
200 | $new_primary_domain->is_https,
201 | true,
202 | $new_primary_domain->active,
203 | DM_DOMAIN_TYPE_MAIN
204 | );
205 |
206 | if ( is_wp_error( $result ) ) {
207 | return false;
208 | }
209 |
210 | return true;
211 | }
212 |
213 | /**
214 | * Change the last changed cache note.
215 | *
216 | * @since 2.1.8
217 | *
218 | * @return void
219 | */
220 | private function update_last_changed() {
221 | wp_cache_set( 'last_changed', microtime(), 'dark-matter' );
222 | }
223 |
224 | /**
225 | * Return the Singleton Instance of the class.
226 | *
227 | * @since 2.0.0
228 | *
229 | * @return DarkMatter_Primary
230 | */
231 | public static function instance() {
232 | static $instance = false;
233 |
234 | if ( ! $instance ) {
235 | $instance = new self();
236 | }
237 |
238 | return $instance;
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/scripts/install-wp-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ $# -lt 3 ]; then
4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]"
5 | exit 1
6 | fi
7 |
8 | DB_NAME=$1
9 | DB_USER=$2
10 | DB_PASS=$3
11 | DB_HOST=${4-localhost}
12 | WP_VERSION=${5-latest}
13 | SKIP_DB_CREATE=${6-false}
14 | WP_TESTS_DOMAIN="darkmatter.test"
15 |
16 | TMPDIR=${TMPDIR-/tmp}
17 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
18 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
19 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress}
20 |
21 | download() {
22 | if [ `which curl` ]; then
23 | curl -s "$1" > "$2";
24 | elif [ `which wget` ]; then
25 | wget -nv -O "$2" "$1"
26 | fi
27 | }
28 |
29 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
30 | WP_BRANCH=${WP_VERSION%\-*}
31 | WP_TESTS_TAG="branches/$WP_BRANCH"
32 |
33 | elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
34 | WP_TESTS_TAG="branches/$WP_VERSION"
35 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
36 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
37 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
38 | WP_TESTS_TAG="tags/${WP_VERSION%??}"
39 | else
40 | WP_TESTS_TAG="tags/$WP_VERSION"
41 | fi
42 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
43 | WP_TESTS_TAG="trunk"
44 | else
45 | # http serves a single offer, whereas https serves multiple. we only want one
46 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
47 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
48 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
49 | if [[ -z "$LATEST_VERSION" ]]; then
50 | echo "Latest WordPress version could not be found"
51 | exit 1
52 | fi
53 | WP_TESTS_TAG="tags/$LATEST_VERSION"
54 | fi
55 | set -ex
56 |
57 | install_wp() {
58 |
59 | if [ -d $WP_CORE_DIR ]; then
60 | return;
61 | fi
62 |
63 | mkdir -p $WP_CORE_DIR
64 |
65 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
66 | mkdir -p $TMPDIR/wordpress-trunk
67 | rm -rf $TMPDIR/wordpress-trunk/*
68 | svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress
69 | mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR
70 | else
71 | if [ $WP_VERSION == 'latest' ]; then
72 | local ARCHIVE_NAME='latest'
73 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
74 | # https serves multiple offers, whereas http serves single.
75 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
76 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
77 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
78 | LATEST_VERSION=${WP_VERSION%??}
79 | else
80 | # otherwise, scan the releases and get the most up to date minor version of the major release
81 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
82 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
83 | fi
84 | if [[ -z "$LATEST_VERSION" ]]; then
85 | local ARCHIVE_NAME="wordpress-$WP_VERSION"
86 | else
87 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
88 | fi
89 | else
90 | local ARCHIVE_NAME="wordpress-$WP_VERSION"
91 | fi
92 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
93 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
94 | fi
95 |
96 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
97 | }
98 |
99 | install_test_suite() {
100 | # portable in-place argument for both GNU sed and Mac OSX sed
101 | if [[ $(uname -s) == 'Darwin' ]]; then
102 | local ioption='-i.bak'
103 | else
104 | local ioption='-i'
105 | fi
106 |
107 | # set up testing suite if it doesn't yet exist
108 | if [ ! -d $WP_TESTS_DIR ]; then
109 | # set up testing suite
110 | mkdir -p $WP_TESTS_DIR
111 | rm -rf $WP_TESTS_DIR/{includes,data}
112 | svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
113 | svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
114 | fi
115 |
116 | if [ ! -f wp-tests-config.php ]; then
117 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
118 | # remove all forward slashes in the end
119 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
120 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
121 | sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
122 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
123 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
124 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
125 | sed $ioption "s/example.org/$WP_TESTS_DOMAIN/" "$WP_TESTS_DIR"/wp-tests-config.php
126 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
127 | fi
128 |
129 | }
130 |
131 | recreate_db() {
132 | shopt -s nocasematch
133 | if [[ $1 =~ ^(y|yes)$ ]]
134 | then
135 | mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA
136 | create_db
137 | echo "Recreated the database ($DB_NAME)."
138 | else
139 | echo "Leaving the existing database ($DB_NAME) in place."
140 | fi
141 | shopt -u nocasematch
142 | }
143 |
144 | create_db() {
145 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
146 | }
147 |
148 | install_db() {
149 |
150 | if [ ${SKIP_DB_CREATE} = "true" ]; then
151 | return 0
152 | fi
153 |
154 | # parse DB_HOST for port or socket references
155 | local PARTS=(${DB_HOST//\:/ })
156 | local DB_HOSTNAME=${PARTS[0]};
157 | local DB_SOCK_OR_PORT=${PARTS[1]};
158 | local EXTRA=""
159 |
160 | if ! [ -z $DB_HOSTNAME ] ; then
161 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
162 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
163 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then
164 | EXTRA=" --socket=$DB_SOCK_OR_PORT"
165 | elif ! [ -z $DB_HOSTNAME ] ; then
166 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
167 | fi
168 | fi
169 |
170 | # create database
171 | if [ $(mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$) ]
172 | then
173 | echo "Reinstalling will delete the existing test database ($DB_NAME)"
174 | read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB
175 | recreate_db $DELETE_EXISTING_DB
176 | else
177 | create_db
178 | fi
179 | }
180 |
181 | install_wp
182 | install_test_suite
183 | install_db
184 |
--------------------------------------------------------------------------------
/tests/phpunit/domain-mapping/DomainsTest.php:
--------------------------------------------------------------------------------
1 | blog_id = $this->factory()->blog->create_object(
33 | [
34 | 'domain' => 'darkmatter.test',
35 | 'path' => '/siteone',
36 | ]
37 | );
38 |
39 | switch_to_blog( $this->blog_id );
40 | }
41 |
42 | /**
43 | * Adding a new domain.
44 | *
45 | * @return void
46 | */
47 | public function test_add_domain() {
48 | $domain = 'mappeddomain1.test';
49 | DarkMatter_Domains::instance()->add( $domain );
50 |
51 | global $wpdb;
52 | $data = $wpdb->get_row(
53 | $wpdb->prepare(
54 | "SELECT * FROM `{$wpdb->base_prefix}domain_mapping` WHERE domain = %s AND blog_id = %d LIMIT 0, 1",
55 | $domain,
56 | $this->blog_id
57 | )
58 | );
59 |
60 | $this->assertNotEmpty( $data );
61 | }
62 |
63 | /**
64 | * Ensure that the same domain cannot be re-added.
65 | *
66 | * @return void
67 | */
68 | public function test_add_add_again() {
69 | $domain = 'mappeddomain1.test';
70 |
71 | /**
72 | * Add the domain.
73 | */
74 | DarkMatter_Domains::instance()->add( $domain );
75 |
76 | /**
77 | * Attempt to add the domain again.
78 | */
79 | $error = DarkMatter_Domains::instance()->add( $domain );
80 |
81 | $this->assertWPError( $error );
82 | $this->assertSame( $error->get_error_code(), 'exists' );
83 | }
84 |
85 | /**
86 | * Test removing a domain.
87 | *
88 | * @return void
89 | */
90 | public function test_delete_domain() {
91 | $domain = 'mappeddomain1.test';
92 |
93 | /**
94 | * Add the domain.
95 | */
96 | DarkMatter_Domains::instance()->add( $domain );
97 |
98 | /**
99 | * Attempt to add the domain again.
100 | */
101 | $return = DarkMatter_Domains::instance()->delete( $domain );
102 |
103 | $this->assertTrue( $return );
104 |
105 | global $wpdb;
106 | $data = $wpdb->get_row(
107 | $wpdb->prepare(
108 | "SELECT * FROM `{$wpdb->base_prefix}domain_mapping` WHERE domain = %s AND blog_id = %d LIMIT 0, 1",
109 | $domain,
110 | $this->blog_id
111 | )
112 | );
113 |
114 | $this->assertEmpty( $data );
115 | }
116 |
117 | /**
118 | * Test finding an existing domain.
119 | *
120 | * @return void
121 | */
122 | public function test_find_domain() {
123 | $domain = 'mappeddomain1.test';
124 |
125 | /**
126 | * Add the domain.
127 | */
128 | DarkMatter_Domains::instance()->add( $domain );
129 |
130 | /**
131 | * Attempt to add the domain again.
132 | */
133 | $return = DarkMatter_Domains::instance()->find( $domain );
134 |
135 | $this->assertNotFalse( $return );
136 | $this->assertEquals( $return->domain, $domain );
137 | }
138 |
139 | /**
140 | * Test updating an existing domain.
141 | *
142 | * @return void
143 | */
144 | public function test_update_domain() {
145 | $domain = 'mappeddomain1.test';
146 |
147 | /**
148 | * Add the domain.
149 | */
150 | DarkMatter_Domains::instance()->add( $domain );
151 |
152 | /**
153 | * Make sure the update did not return a WP_Error.
154 | */
155 | $return = DarkMatter_Domains::instance()->update( $domain, true, true );
156 | $this->assertNotWPError( $return );
157 |
158 | /**
159 | * Assert the update did actually work.
160 | */
161 | global $wpdb;
162 | $data = $wpdb->get_row(
163 | $wpdb->prepare(
164 | "SELECT * FROM `{$wpdb->base_prefix}domain_mapping` WHERE domain = %s AND blog_id = %d LIMIT 0, 1",
165 | $domain,
166 | $this->blog_id
167 | )
168 | );
169 |
170 | $this->assertEquals( '1', $data->is_primary );
171 | $this->assertEquals( '1', $data->is_https );
172 | }
173 |
174 | /**
175 | * Test international domains, such as Chinese, which have a system for handling non-ASCII characters.
176 | *
177 | * @return void
178 | */
179 | public function test_validation_international_domains() {
180 | /** Chinese - Unicode - Invalid */
181 | $return = DarkMatter_Domains::instance()->add( 'www.例如.中国' );
182 | $this->assertWPError( $return, 'Chinese - Unicode - Invalid' );
183 | $this->assertSame( $return->get_error_code(), 'domain' );
184 |
185 | /** Chinese - ASCII - Valid */
186 | $return = DarkMatter_Domains::instance()->add( 'www.xn--fsqu6v.xn--fiqs8s' );
187 | $this->assertNotWPError( $return, 'Chinese - ASCII - Valid' );
188 | }
189 |
190 | /**
191 | * Invalid domains and input that should not be allowed.
192 | *
193 | * @return void
194 | */
195 | public function test_validation_invalid_domains() {
196 | /** Empty */
197 | $return = DarkMatter_Domains::instance()->add( '' );
198 | $this->assertWPError( $return );
199 | $this->assertSame( $return->get_error_code(), 'empty' );
200 |
201 | /** URI */
202 | $return = DarkMatter_Domains::instance()->add( 'http://example.com/' );
203 | $this->assertWPError( $return );
204 | $this->assertSame( $return->get_error_code(), 'unsure' );
205 |
206 | /** Domain + Path */
207 | $return = DarkMatter_Domains::instance()->add( 'example.com/hello-world' );
208 | $this->assertWPError( $return );
209 | $this->assertSame( $return->get_error_code(), 'unsure' );
210 |
211 | /** Domain + Port */
212 | $return = DarkMatter_Domains::instance()->add( 'example.com:443' );
213 | $this->assertWPError( $return );
214 | $this->assertSame( $return->get_error_code(), 'unsure' );
215 |
216 | /** DOMAIN_CURRENT_SITE */
217 | $return = DarkMatter_Domains::instance()->add( 'darkmatter.test' );
218 | $this->assertWPError( $return );
219 | $this->assertSame( $return->get_error_code(), 'wp-config' );
220 |
221 | /** Non-ASCII - i.e. emojis, etc. */
222 | $return = DarkMatter_Domains::instance()->add( '🐲' );
223 | $this->assertWPError( $return );
224 | $this->assertSame( $return->get_error_code(), 'domain' );
225 |
226 | /** Stored XSS (and curious input from an administrator one time ... ... ...) */
227 | $return = DarkMatter_Domains::instance()->add( '' );
228 | $this->assertWPError( $return );
229 | $this->assertSame( $return->get_error_code(), 'unsure' );
230 | }
231 |
232 | /**
233 | * Test valid domains.
234 | *
235 | * @return void
236 | */
237 | public function test_validation_valid_domains() {
238 | /**
239 | * Valid domains
240 | * =============
241 | */
242 |
243 | /** Localhost */
244 | $return = DarkMatter_Domains::instance()->add( 'localhost' );
245 | $this->assertNotWPError( $return );
246 |
247 | /** Example domain */
248 | $return = DarkMatter_Domains::instance()->add( 'example.com' );
249 | $this->assertNotWPError( $return );
250 |
251 | /** Example sub-domain */
252 | $return = DarkMatter_Domains::instance()->add( 'www.example.com' );
253 | $this->assertNotWPError( $return );
254 |
255 | /** Atypical test domain. */
256 | $return = DarkMatter_Domains::instance()->add( 'development.test' );
257 | $this->assertNotWPError( $return );
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/domain-mapping/api/class-darkmatter-restrict.php:
--------------------------------------------------------------------------------
1 | restrict_table = $wpdb->base_prefix . 'domain_restrict';
34 | }
35 |
36 | /**
37 | * Perform basic checks before committing to a action performed by a method.
38 | *
39 | * @since 2.0.0
40 | *
41 | * @param string $fqdn Fully qualified domain name.
42 | * @return WP_Error|boolean True on pass. WP_Error on failure.
43 | */
44 | private function _basic_checks( $fqdn ) {
45 | if ( empty( $fqdn ) ) {
46 | return new WP_Error( 'empty', __( 'Please include a fully qualified domain name to be added.', 'dark-matter' ) );
47 | }
48 |
49 | /**
50 | * Ensure that the URL is purely a domain. In order for the parse_url() to work, the domain must be prefixed
51 | * with a double forward slash.
52 | */
53 | if ( false === stripos( $fqdn, '//' ) ) {
54 | // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
55 | $domain_parts = parse_url( '//' . ltrim( $fqdn, '/' ) );
56 | } else {
57 | // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
58 | $domain_parts = parse_url( $fqdn );
59 | }
60 |
61 | if ( ! empty( $domain_parts['path'] ) || ! empty( $domain_parts['port'] ) || ! empty( $domain_parts['query'] ) ) {
62 | return new WP_Error( 'unsure', __( 'The domain provided contains path, port, or query string information. Please removed this before continuing.', 'dark-matter' ) );
63 | }
64 |
65 | $fqdn = $domain_parts['host'];
66 |
67 | if ( defined( 'DOMAIN_CURRENT_SITE' ) && DOMAIN_CURRENT_SITE === $fqdn ) {
68 | return new WP_Error( 'wp-config', __( 'You cannot configure the WordPress Network primary domain.', 'dark-matter' ) );
69 | }
70 |
71 | $domains = DarkMatter_Domains::instance();
72 | if ( $domains->is_exist( $fqdn ) ) {
73 | return new WP_Error( 'used', __( 'This domain is in use.', 'dark-matter' ) );
74 | }
75 |
76 | return $fqdn;
77 | }
78 |
79 | /**
80 | * Add a domain to the Restrict list.
81 | *
82 | * @since 2.0.0
83 | *
84 | * @param string $fqdn Domain to be added to the reserve list.
85 | * @return WP_Error|boolean True on success, WP_Error otherwise.
86 | */
87 | public function add( $fqdn = '' ) {
88 | $fqdn = $this->_basic_checks( $fqdn );
89 |
90 | if ( is_wp_error( $fqdn ) ) {
91 | return $fqdn;
92 | }
93 |
94 | if ( $this->is_exist( $fqdn ) ) {
95 | return new WP_Error( 'exists', __( 'The Domain is already Restricted.', 'dark-matter' ) );
96 | }
97 |
98 | /**
99 | * Add the domain to the database.
100 | */
101 | global $wpdb;
102 |
103 | // phpcs:ignore
104 | $result = $wpdb->insert(
105 | $this->restrict_table,
106 | array(
107 | 'domain' => $fqdn,
108 | ),
109 | array( '%s' )
110 | );
111 |
112 | if ( ! $result ) {
113 | return new WP_Error( 'unknown', __( 'An unknown error has occurred. The domain has not been removed from the Restrict list.', 'dark-matter' ) );
114 | }
115 |
116 | $this->refresh_cache();
117 |
118 | /**
119 | * Fires when a domain is added to the restricted list.
120 | *
121 | * @since 2.0.0
122 | *
123 | * @param string $fqdn Domain name that was restricted.
124 | */
125 | do_action( 'darkmatter_restrict_add', $fqdn );
126 |
127 | return true;
128 | }
129 |
130 | /**
131 | * Delete a domain to the Restrict list.
132 | *
133 | * @since 2.0.0
134 | *
135 | * @param string $fqdn Domain to be deleted to the restrict list.
136 | * @return WP_Error|boolean True on success, WP_Error otherwise.
137 | */
138 | public function delete( $fqdn = '' ) {
139 | $fqdn = $this->_basic_checks( $fqdn );
140 |
141 | if ( is_wp_error( $fqdn ) ) {
142 | return $fqdn;
143 | }
144 |
145 | if ( ! $this->is_exist( $fqdn ) ) {
146 | return new WP_Error( 'missing', __( 'The Domain is not found in the Restrict list.', 'dark-matter' ) );
147 | }
148 |
149 | /**
150 | * Remove the domain to the database.
151 | */
152 | global $wpdb;
153 |
154 | // phpcs:ignore
155 | $result = $wpdb->delete(
156 | $this->restrict_table,
157 | array(
158 | 'domain' => $fqdn,
159 | ),
160 | array( '%s' )
161 | );
162 |
163 | if ( ! $result ) {
164 | return new WP_Error( 'unknown', __( 'An unknown error has occurred. The domain has not been removed from the Restrict list.', 'dark-matter' ) );
165 | }
166 |
167 | $this->refresh_cache();
168 |
169 | /**
170 | * Fires when a domain is deleted from the restricted list.
171 | *
172 | * @since 2.0.0
173 | *
174 | * @param string $fqdn Domain name that was restricted.
175 | */
176 | do_action( 'darkmatter_restrict_delete', $fqdn );
177 |
178 | return true;
179 | }
180 |
181 | /**
182 | * Retrieve all restrict domains.
183 | *
184 | * @since 2.0.0
185 | *
186 | * @return array List of restrict domains.
187 | */
188 | public function get() {
189 | /**
190 | * Attempt to retreive the domain from cache.
191 | */
192 | $restrict_domains = wp_cache_get( 'restricted', 'dark-matter' );
193 |
194 | /**
195 | * Fires after the domains have been retrieved from cache (if available)
196 | * and before the database is used to retrieve the Restricted domains.
197 | *
198 | * @since 2.0.0
199 | *
200 | * @param array $restricted_domains Restricted domains retrieved from Object Cache.
201 | */
202 | $restrict_domains = apply_filters( 'darkmatter_restricted_get', $restrict_domains );
203 |
204 | if ( $restrict_domains ) {
205 | return $restrict_domains;
206 | }
207 |
208 | /**
209 | * Then attempt to retrieve the domains from the database, assuming
210 | * there is any.
211 | */
212 | global $wpdb;
213 |
214 | // phpcs:ignore
215 | $restricted_domains = $wpdb->get_col( "SELECT domain FROM {$this->restrict_table} ORDER BY domain" );
216 |
217 | if ( empty( $restricted_domains ) ) {
218 | $restricted_domains = array();
219 | }
220 |
221 | /**
222 | * May seem peculiar to cache an empty array here but as this will
223 | * likely be a slow changing data set, then it's pointless to keep
224 | * pounding the database unnecessarily.
225 | */
226 | wp_cache_add( 'restricted', $restricted_domains, 'dark-matter' );
227 |
228 | return $restricted_domains;
229 | }
230 |
231 | /**
232 | * Check if a domain has been restricted.
233 | *
234 | * @since 2.0.0
235 | *
236 | * @param string $fqdn Domain to check.
237 | * @return boolean True if found. False otherwise.
238 | */
239 | public function is_exist( $fqdn = '' ) {
240 | if ( empty( $fqdn ) ) {
241 | return false;
242 | }
243 |
244 | $restricted_domains = $this->get();
245 |
246 | return in_array( $fqdn, $restricted_domains );
247 | }
248 |
249 | /**
250 | * Helper method to refresh the cache for Restricted domains.
251 | *
252 | * @since 2.0.0
253 | *
254 | * @return void
255 | */
256 | public function refresh_cache() {
257 | wp_cache_delete( 'restricted', 'dark-matter' );
258 | $this->get();
259 | }
260 |
261 | /**
262 | * Return the Singleton Instance of the class.
263 | *
264 | * @since 2.0.0
265 | *
266 | * @return DarkMatter_Restrict
267 | */
268 | public static function instance() {
269 | static $instance = false;
270 |
271 | if ( ! $instance ) {
272 | $instance = new self();
273 | }
274 |
275 | return $instance;
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Dark Matter
2 |
3 | Dark Matter is a highly opinionated domain mapping plugin for WordPress
4 | Networks, designed to work out of the box as-is with no setup. Unlike other
5 | plugins such as Donncha's "WordPress MU Domain Mapping" and WPMU Dev's premium
6 | domain mapping plugin, Dark Matter offers virtually no options beyond mapping
7 | individual domains.
8 |
9 | ## Constants
10 |
11 | ### Set Media Domains
12 |
13 | Media Domains can be setup through the CLI commands on a per site basis, or setup for all sites on a Multisite using a
14 | constant. The constant can support one or more domains.
15 |
16 | ```php
17 | define( 'DM_NETWORK_MEDIA', [
18 | 'media1.example.com',
19 | /** 'media2.example.com', */
20 | /** 'media3.example.com', */
21 | ] );
22 | ```
23 |
24 | ## CLI Commands
25 |
26 | ### Add / Update / Remove Domains
27 |
28 | Examples of adding, removing and updating a domain for a Site.
29 |
30 | ```
31 | wp --url="sites.my.com/siteone" darkmatter domain add www.example.com --primary --https
32 | wp --url="sites.my.com/siteone" darkmatter domain remove www.example.com
33 | wp --url="sites.my.com/siteone" darkmatter domain remove www.example.com --force
34 | wp --url="sites.my.com/siteone" darkmatter domain set www.example.com --primary
35 | wp --url="sites.my.com/siteone" darkmatter domain set www.example.com --secondary
36 | ```
37 |
38 | ### Add / Update / Remove Media Domains
39 |
40 | Set a media domain for a site.
41 | ```
42 | wp --url="sites.my.com/sitefifteen" darkmatter domain add example.mycdn.com --type=media
43 | ```
44 |
45 | Convert a secondary domain into a media domain. Useful for when repurposing an old domain for use a CDN for media assets.
46 | ```
47 | wp --url="sites.my.com/siteone" darkmatter domain set example.mycdn.com --type=media
48 | ```
49 |
50 | Convert a Media domain to a main domain. This is useful in scenarios when a media domain is redundant and to ensure it redirects to the website.
51 | ```
52 | wp --url="sites.my.com/siteone" darkmatter domain set secondarydomain.com --type=main --secondary
53 | ```
54 |
55 | ### Listing Domains
56 |
57 | Examples of listing domains for a Site.
58 |
59 | ```
60 | wp --url="sites.my.com/siteone" darkmatter domain list
61 | wp --url="sites.my.com/siteone" darkmatter domain list --format=json
62 | ```
63 |
64 | Examples of listing domains for the entire Network.
65 |
66 | ```
67 | wp darkmatter domain list
68 | wp darkmatter domain list --format=csv
69 | ```
70 |
71 | Retrieve all the primary domains for the Network.
72 |
73 | ```
74 | wp darkmatter domain list --primary
75 | ```
76 |
77 | ### Reserving Domains
78 |
79 | Reserving a domain. This allows an administrator to setup the primary and / or secondary domains but stop Dark Matter performing redirects and rewrites. Please note; domains are enabled by default.
80 |
81 | ```
82 | wp --url="sites.my.com/siteone" darkmatter domain add www.example.com --primary --https --disable
83 | wp --url="sites.my.com/siteone" darkmatter domain set www.example.com --enable
84 | wp --url="sites.my.com/siteone" darkmatter domain set www.example.com --disable
85 | ```
86 |
87 | ### Restricting Domains
88 |
89 | Examples of adding and removing a restricted domains for a Network. This permits administrators to stop domains from being used for a WordPress website; useful for organisations which use multiple CMS.
90 |
91 | ```
92 | wp darkmatter restrict add www.example.com
93 | wp darkmatter restrict remove www.example.com
94 | ```
95 |
96 | Examples of retrieving a list of all restricted domains for a Network.
97 |
98 | ```
99 | wp darkmatter restrict list
100 | wp darkmatter restrict list --format=json
101 | wp darkmatter restrict list --format=ids
102 | ```
103 |
104 | ## Reporting problems
105 |
106 | You can use the Issues system here on Github to report problems with the Dark
107 | Matter plugin. To aid and speed-up diagnosing the problem, you are best to
108 | include as much as the following as you possibly can;
109 |
110 | * Check here to ensure the problem has not been reported by someone else.
111 | * WordPress;
112 | * Version of WordPress itself.
113 | * List of active plugins (installed but unused _should_ rarely cause problems)
114 | * Hosting information;
115 | * Either
116 | * Apache / Nginx
117 | * Operating System (Linux or Windows)
118 | * Or;
119 | * Host provider (Digital Ocean, Dreamhosts, GoDaddy, WP Engine, etc)
120 | * Browser (Chrome, IE, Firefox, Opera, etc with version)
121 | * Any additional information such as;
122 | * Using Cloudflare.
123 |
124 | All issues reported are taken seriously and are checked, but please bear in mind
125 | that responses are not always instant.
126 |
127 | ### Syntax
128 |
129 | Dark Matter does not have a coding style guide but there are several rules which
130 | should be observed;
131 |
132 | * Unix line breaks.
133 | * Tabs, not spaces.
134 | * [Yoda conditions](https://en.wikipedia.org/wiki/Yoda_conditions).
135 | * Code should be concise rather than terse.
136 | * Comments should not extend beyond the 80th character (default in Atom) unless;
137 | * Text for a bullet point.
138 | * Code example in comments.
139 | * PHPDoc conventions for @@link or @@param in describing a class or function.
140 |
141 | ### URLs to check
142 |
143 | The following is a list of example URLs which are worth checking (depending on
144 | the change) when developing with Dark Matter.
145 |
146 | Using www.wpnetwork.com as the "Admin domain" and www.example.com as the
147 | "Primary domain", Dark Matter should be tested with the following URLs;
148 |
149 | * http://www.wpnetwork.com/sitetwo/ (with trailing forward slash) => http://www.example.com/
150 | * http://www.wpnetwork.com/sitetwo (without trailing forward slash) => http://www.example.com/
151 | * http://www.wpnetwork.com/sitetwo/index.php (query string processing, without trailing forward slash) => http://www.example.com/
152 | * http://www.wpnetwork.com/sitetwo/index.php/ (query string processing, with trailing forward slash) => http://www.example.com/
153 | * http://www.wpnetwork.com/sitetwo/?utm_source=test (with query string) => http://www.example.com/?utm_source=test
154 | * http://www.wpnetwork.com/sitetwo?utm_source=test (with query string, without trailing forward slash) => http://www.example.com/?utm_source=test
155 | * http://www.wpnetwork.com/sitetwo/#test (hash URL test) => http://www.example.com/
156 | * http://www.wpnetwork.com/sitetwo#test (hash URL test, without trailing forward slash) => http://www.example.com/
157 |
158 | ## Development
159 |
160 | ### Prerequisites
161 |
162 | To develop with Dark Matter plugin requires the following to be installed.
163 |
164 | * Composer 2.0+
165 | * Node 16.13+
166 | * PHP 7.0+
167 | * WordPress 5.9+
168 |
169 | For a developer environment, Dark Matter plugin is most commonly built with VVV. However this is not a hard requirement
170 | and any WordPress development environment should work without any complications.
171 |
172 | ### Initial Setup
173 |
174 | If you have NVM installed, then you can ensure the most recent compatible version of Node is installed and ready for
175 | use.
176 |
177 | ```shell
178 | nvm use
179 | ```
180 |
181 | And then you can run the following:
182 |
183 | ```shell
184 | npm run start
185 | ```
186 |
187 | This NPM script will do the following:
188 |
189 | * Install all Composer dependencies, including dev dependencies.
190 | * Install all NPM dependencies, including dev dependencies.
191 | * Build both the production and developer scripts.
192 |
193 | After this point, the plugin is ready for use with your local WordPress installation.
194 |
195 | ### Unit Tests
196 |
197 | Use the following command to setup PHPUnit and the WordPress environment for it.
198 |
199 | ```shell
200 | composer run test-setup
201 | ```
202 |
203 | After the setup has completed successfully, then the following command can be used to run the unit tests.
204 |
205 | ```shell
206 | composer run test
207 | ```
--------------------------------------------------------------------------------
/domain-mapping/classes/class-dm-healthchecks.php:
--------------------------------------------------------------------------------
1 | __( 'Dark Matter - Domain Mapping - Checking Sunrise dropin', 'dark-matter' ),
37 | 'test' => [ $this, 'test_dropin' ],
38 | ];
39 |
40 | $tests['direct']['darkmatter_domain_mapping_ssl'] = [
41 | 'label' => __( 'Dark Matter - Domain Mapping - Checking SSL configuration', 'dark-matter' ),
42 | 'test' => [ $this, 'test_ssl' ],
43 | ];
44 |
45 | if ( ! is_main_site() ) {
46 | $tests['direct']['darkmatter_domain_mapping_primary_domain_set'] = [
47 | 'label' => __( 'Dark Matter - Domain Mapping - Checking for primary domain', 'dark-matter' ),
48 | 'test' => [ $this, 'test_primary_domain_set' ],
49 | ];
50 | }
51 |
52 | $tests['direct']['darkmatter_domain_mapping_cookie_domain'] = [
53 | 'label' => __( 'Dark Matter - Domain Mapping - Checking for cookie domain settings', 'dark-matter' ),
54 | 'test' => [ $this, 'test_cookie_domain' ],
55 | ];
56 |
57 | return $tests;
58 | }
59 |
60 | /**
61 | * Ensures that the COOKIE_DOMAIN constant is set by Dark Matter and not set elsewhere (such as wp-config.php).
62 | *
63 | * @since 2.1.0
64 | *
65 | * @return bool True if COOKIE_DOMAIN is set by Dark Matter. False otherwise.
66 | */
67 | public function cookie_domain_dm_set() {
68 | return ( defined( 'DARKMATTER_COOKIE_SET' ) && DARKMATTER_COOKIE_SET );
69 | }
70 |
71 | /**
72 | * Checks to ensure the dropin - sunrise.php - exists.
73 | *
74 | * @since 2.1.0
75 | *
76 | * @return bool True if sunrise.php exists. False otherwise.
77 | */
78 | public function dropin_exists() {
79 | return file_exists( DM_PATH . '/domain-mapping/sunrise.php' );
80 | }
81 |
82 | /**
83 | * Checks to ensure the constant `FORCE_SSL_ADMIN` is configured correctly for Dark Matter.
84 | *
85 | * @since 2.1.0
86 | *
87 | * @return bool True if `FORCE_SSL_ADMIN` is present and set. False otherwise.
88 | */
89 | public function force_ssl_set() {
90 | return defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN;
91 | }
92 |
93 | /**
94 | * Checks the dropin - sunrise.php - to see if it is the correct version.
95 | *
96 | * @since 2.1.0
97 | *
98 | * @return bool True if the dropin is the correct version. False otherwise.
99 | */
100 | public function is_dropin_latest() {
101 | $destination = WP_CONTENT_DIR . '/sunrise.php';
102 | $source = DM_PATH . '/domain-mapping/sunrise.php';
103 |
104 | return filesize( $destination ) === filesize( $source ) && md5_file( $destination ) === md5_file( $source );
105 | }
106 |
107 | /**
108 | * Checks the COOKIE_DOMAIN constant to ensure it is compatible with Dark Matter.
109 | *
110 | * @since 2.1.0
111 | *
112 | * @return array Test result.
113 | */
114 | public function test_cookie_domain() {
115 | $result = [
116 | 'label' => __( 'Dark Matter single-sign on (bringing the admin bar to the public-facing side) is enabled.', 'dark-matter' ),
117 | 'status' => 'good',
118 | 'badge' => array(
119 | 'label' => __( 'Domain Mapping', 'dark-matter' ),
120 | 'color' => 'green',
121 | ),
122 | 'description' => sprintf(
123 | '
%s
',
124 | __( 'Dark Matter single-sign on is enabled and can load the admin bar when WordPress users are visiting the public-facing side of your site.', 'dark-matter' )
125 | ),
126 | 'actions' => '',
127 | 'test' => 'darkmatter_domain_mapping_cookie_domain',
128 | ];
129 |
130 | if ( ! $this->cookie_domain_dm_set() ) {
131 | $result['label'] = __( 'The cookie domain constant has been set and Dark Matter SSO has been disabled.', 'dark-matter' );
132 | $result['badge']['color'] = 'red';
133 | $result['status'] = 'critical';
134 | $result['description'] = sprintf(
135 | '
%s
',
136 | sprintf(
137 | /* translators: COOKIE_DOMAIN constant */
138 | __( 'The %1$s constant has been set, likely within your wp-config.php file. Dark Matter single-sign on (SSO) - which uses %1$s - has been disabled to prevent errors.', 'dark-matter' ),
139 | 'COOKIE_DOMAIN'
140 | )
141 | );
142 | }
143 |
144 | return $result;
145 | }
146 |
147 | /**
148 | * Checks the Sunrise dropin to ensure it is configured correctly and is up-to-date.
149 | *
150 | * @since 2.1.0
151 | *
152 | * @return array Test result.
153 | */
154 | public function test_dropin() {
155 | $result = [
156 | 'label' => __( 'Sunrise dropin is enabled and up-to-date.', 'dark-matter' ),
157 | 'status' => 'good',
158 | 'badge' => array(
159 | 'label' => __( 'Domain Mapping', 'dark-matter' ),
160 | 'color' => 'green',
161 | ),
162 | 'description' => sprintf(
163 | '
%s
',
164 | __( 'Sunrise is the name of the dropin file which maps custom domains to your WordPress sites.', 'dark-matter' )
165 | ),
166 | 'actions' => '',
167 | 'test' => 'darkmatter_domain_mapping_dropin',
168 | ];
169 |
170 | if ( ! $this->dropin_exists() ) {
171 | $result['label'] = __( 'Sunrise dropin cannot be found.', 'dark-matter' );
172 | $result['badge']['color'] = 'red';
173 | $result['status'] = 'critical';
174 | $result['description'] = sprintf(
175 | '
%s
',
176 | __( 'Contact your system administrator to add sunrise.php to your wp-content/ folder.', 'dark-matter' )
177 | );
178 |
179 | return $result;
180 | }
181 |
182 | if ( ! defined( 'SUNRISE' ) ) {
183 | $result['label'] = __( 'SUNRISE constant is not setup.', 'dark-matter' );
184 | $result['badge']['color'] = 'red';
185 | $result['status'] = 'critical';
186 | $result['description'] = sprintf(
187 | '
%s
',
188 | sprintf(
189 | /* translators: SUNRISE constant */
190 | __( 'Please ensure the %1$s constant is present and set to "true" in your wp-config.php file.', 'dark-matter' ),
191 | 'SUNRISE'
192 | )
193 | );
194 |
195 | return $result;
196 | }
197 |
198 | if ( ! $this->is_dropin_latest() ) {
199 | $result['label'] = __( 'Your Sunrise dropin does not match the Dark Matter version.', 'dark-matter' );
200 | $result['badge']['color'] = 'orange';
201 | $result['status'] = 'recommended';
202 | $result['description'] = sprintf(
203 | '
%s
',
204 | __( 'Sunrise dropin is different from the version recommended by Dark Matter. Please update sunrise.php to the version found in Dark Matter plugin folder.', 'dark-matter' )
205 | );
206 | }
207 |
208 | return $result;
209 | }
210 |
211 | /**
212 | * Checks the Sunrise dropin to ensure it is configured correctly and is up-to-date.
213 | *
214 | * @since 2.1.0
215 | *
216 | * @return array Test result.
217 | */
218 | public function test_primary_domain_set() {
219 | $result = [
220 | 'label' => __( 'You have a primary domain.', 'dark-matter' ),
221 | 'status' => 'good',
222 | 'badge' => array(
223 | 'label' => __( 'Domain Mapping', 'dark-matter' ),
224 | 'color' => 'green',
225 | ),
226 | 'description' => '',
227 | 'actions' => '',
228 | 'test' => 'darkmatter_domain_mapping_primary_domain_set',
229 | ];
230 |
231 | $primary = DarkMatter_Primary::instance()->get();
232 |
233 | if ( empty( $primary ) ) {
234 | $result['label'] = __( 'You have not a set a primary domain.', 'dark-matter' );
235 | $result['badge']['color'] = 'orange';
236 | $result['status'] = 'recommended';
237 | $result['description'] = sprintf(
238 | '
%s
',
239 | sprintf(
240 | /* translators: link to unmapped homepage url */
241 | __( 'No primary domain is set. Currently this site is can only be visited on the admin domain at; %1$s.', 'dark-matter' ),
242 | sprintf(
243 | '%1$s',
244 | home_url()
245 | )
246 | )
247 | );
248 | } else {
249 | $result['description'] = sprintf(
250 | '
',
286 | /* translators: Documentation explaining HTTPS and why it should be used. */
287 | esc_url( __( 'https://wordpress.org/support/article/why-should-i-use-https/' ) ),
288 | __( 'Learn more about why you should use HTTPS' ),
289 | /* translators: Accessibility text. */
290 | __( '(opens in a new tab)' )
291 | ),
292 | 'test' => 'darkmatter_domain_mapping_ssl',
293 | ];
294 |
295 | if ( ! $this->force_ssl_set() ) {
296 | $result['label'] = __( 'WordPress does not redirect admin requests to HTTPS.', 'dark-matter' );
297 | $result['badge']['color'] = 'red';
298 | $result['status'] = 'critical';
299 | $result['description'] = sprintf(
300 | '
%s
',
301 | sprintf(
302 | /* translators: FORCE_SSL_ADMIN constant */
303 | __( 'Please ensure the %1$s constant is present and set to "true" in your wp-config.php file.', 'dark-matter' ),
304 | 'FORCE_SSL_ADMIN'
305 | )
306 | );
307 | }
308 |
309 | return $result;
310 | }
311 |
312 | /**
313 | * Return the Singleton Instance of the class.
314 | *
315 | * @since 2.1.0
316 | *
317 | * @return DM_HealthChecks
318 | */
319 | public static function instance() {
320 | static $instance = false;
321 |
322 | if ( ! $instance ) {
323 | $instance = new self();
324 | }
325 |
326 | return $instance;
327 | }
328 | }
329 | DM_HealthChecks::instance();
330 |
--------------------------------------------------------------------------------
/domain-mapping/classes/class-dm-media.php:
--------------------------------------------------------------------------------
1 | sites[ $this->current_site_id ] ) && false !== $this->sites[ $this->current_site_id ];
63 | }
64 |
65 | /**
66 | * Get the main domains for a particular site.
67 | *
68 | * @param integer $site_id Site ID to retrieve the main domains for.
69 | * @return array Main domains, essentially the unmapped (admin) domain and the primary (mapped) domain.
70 | */
71 | private function get_main_domains( $site_id = 0 ) {
72 | /**
73 | * Ensure the site is actually a site.
74 | */
75 | $blog = get_site( $site_id );
76 | if ( ! is_a( $blog, 'WP_Site' ) ) {
77 | return [];
78 | }
79 |
80 | $unmapped = untrailingslashit( $blog->domain . $blog->path );
81 |
82 | /**
83 | * Put together the main domains.
84 | */
85 | $main_domains = [
86 | $unmapped,
87 | ];
88 |
89 | $primary = DarkMatter_Primary::instance()->get( $site_id );
90 | if ( ! empty( $primary ) ) {
91 | $main_domains[] = $primary->domain;
92 | }
93 |
94 | return $main_domains;
95 | }
96 |
97 | /**
98 | * Convert WordPress Core's allowed mime types array, which has keys designed for regex, to straight-forward strings
99 | * for the individual extensions as keys on the array.
100 | *
101 | * For example: turn `image/jpeg` mime type key from `jpg|jpeg|jpe` into three separate key / values on the array.
102 | *
103 | * @return array All mime types and extensions.
104 | *
105 | * @since 2.2.0
106 | */
107 | private function get_mime_types() {
108 | $mime_types = get_allowed_mime_types();
109 |
110 | foreach ( $mime_types as $extension => $mime_type ) {
111 | /**
112 | * No divided - regex OR - then skip it.
113 | */
114 | if ( false === stripos( $extension, '|' ) ) {
115 | continue;
116 | }
117 |
118 | /**
119 | * Get the separate extensions.
120 | */
121 | $extensions = explode( '|', $extension );
122 |
123 | /**
124 | * Add to the array.
125 | */
126 | foreach ( $extensions as $ext ) {
127 | $mime_types[ $ext ] = $mime_type;
128 | }
129 | }
130 |
131 | return $mime_types;
132 | }
133 |
134 | /**
135 | * Initialise the Media setup.
136 | *
137 | * @return void
138 | *
139 | * @since 2.2.0
140 | */
141 | public function init() {
142 | $this->current_site_id = get_current_blog_id();
143 | $this->request_main_domains = $this->get_main_domains( $this->current_site_id );
144 |
145 | $this->prime_site( $this->current_site_id );
146 | }
147 |
148 | /**
149 | * Clean up the `post_content` to restore the data to the original state as if Dark Matter was not present.
150 | *
151 | * @param array $data An array of slashed, sanitized, and processed post data.
152 | * @return array Post data, with URLs unmapped.
153 | *
154 | * @since 2.2.0
155 | */
156 | public function insert_post( $data = [] ) {
157 | if ( ! empty( $data['post_content'] ) ) {
158 | $data['post_content'] = $this->unmap( $data['post_content'] );
159 | }
160 |
161 | return $data;
162 | }
163 |
164 | /**
165 | * Map Media domains where appropriate.
166 | *
167 | * @param string $content Content containing URLs - or a URL - to be adjusted.
168 | * @return string
169 | *
170 | * @since 2.2.0
171 | */
172 | public function map( $content = '' ) {
173 | if ( ! $this->can_map() || empty( $content ) ) {
174 | return $content;
175 | }
176 |
177 | $domains_regex = implode( '|', $this->sites[ $this->current_site_id ]['main_domains'] );
178 |
179 | /**
180 | * Find all URLs which are not mapped to a Media domain, but are either on the primary domain or admin domain
181 | * (to avoid confusion with a third party image, like GIPHY), and process them.
182 | *
183 | * Note on the regular expression: This regex looks a bit odd, and basically it's to find all URLs after the
184 | * protocol until it hits a character we do not want, like closing double-quote (") on a tag or whitespace.
185 | * Testing different expressions, some more concise, this was the most performant in a small generated sample of
186 | * `post_content` with a four-image gallery.
187 | *
188 | * * https?://(?:mappeddomain1\.test|helloworld\.com)/[^">\s]+ - 1,130 steps (https://regex101.com/r/mrEb9U/1)
189 | * * http?s:\/\/(?:.*?)\.(?:jpg|jpeg|gif|png|pdf) - 7,283 steps (https://regex101.com/r/XPkFIA/1)
190 | * * ([-a-z0-9_\/:.]+\.(jpg|jpeg|png)) - 11,593 steps (https://regex101.com/r/mGlUIH/1)
191 | *
192 | * There will no doubt be some pitfalls, but the performance profile of this approach seems to outweigh those
193 | * concerns (at the time of writing this).
194 | */
195 | $urls = [];
196 | preg_match_all( "#https?://(?:{$domains_regex})/[^\"'>\s]+#", $content, $urls );
197 |
198 | /**
199 | * Remove any duplicates.
200 | */
201 | $urls = array_unique( $urls );
202 |
203 | /**
204 | * No URLs, skip.
205 | */
206 | if ( empty( $urls ) || empty( $urls[0] ) ) {
207 | return $content;
208 | }
209 |
210 | /**
211 | * Loop through all the URLs and replace as required.
212 | */
213 | foreach ( $urls[0] as $url ) {
214 | $extension = pathinfo( $url, PATHINFO_EXTENSION );
215 |
216 | /**
217 | * No extension, then we can ignore it.
218 | */
219 | if ( empty( $extension ) ) {
220 | continue;
221 | }
222 |
223 | /**
224 | * Check for a valid extension.
225 | */
226 | if ( array_key_exists( strtolower( $extension ), $this->sites[ $this->current_site_id ]['allowed_mimes'] ) ) {
227 | $content = str_ireplace( $url, $this->map_url( $url ), $content );
228 | }
229 | }
230 |
231 | return $content;
232 | }
233 |
234 | /**
235 | * Used to map a URL to a Media domain. Any URL passed into this method **will** be mapped.
236 | *
237 | * @param string $url URL to be modified.
238 | * @return string URL with the domain changed to be from a Media domain.
239 | *
240 | * @since 2.2.0
241 | */
242 | public function map_url( $url = '' ) {
243 | /**
244 | * No Media domains or the URL is blank, then bail.
245 | */
246 | if ( ! $this->can_map() || empty( $url ) ) {
247 | return $url;
248 | }
249 |
250 | /**
251 | * Check to ensure the URL is on a domain we can map. This is also used to prevent double-mapping occurring.
252 | */
253 | $do_map = false;
254 |
255 | foreach ( $this->sites[ $this->current_site_id ]['main_domains'] as $main_domain ) {
256 | if ( false !== stripos( $url, $main_domain ) ) {
257 | $do_map = true;
258 | break;
259 | }
260 | }
261 |
262 | if ( ! $do_map ) {
263 | return $url;
264 | }
265 |
266 | /**
267 | * Alternate through the Media domains if there is more than one.
268 | */
269 | $index = 0;
270 |
271 | if ( $this->sites[ $this->current_site_id ]['media_domains_count'] > 1 ) {
272 | $index = wp_rand( 0, $this->sites[ $this->current_site_id ]['media_domains_count'] );
273 | }
274 |
275 | $url = preg_replace(
276 | '#://(' . implode( '|', $this->sites[ $this->current_site_id ]['main_domains'] ) . ')#',
277 | '://' . untrailingslashit( $this->sites[ $this->current_site_id ]['media_domains'][ $index ]->domain ),
278 | $url
279 | );
280 |
281 | /**
282 | * Ensure the URL is HTTPS.
283 | */
284 | return set_url_scheme( $url, 'https' );
285 | }
286 |
287 | /**
288 | * Apply filters to map / unmap asset domains on REST API.
289 | *
290 | * @return void
291 | *
292 | * @since 2.2.0
293 | */
294 | public function prepare_rest() {
295 | /**
296 | * Loop all post types with REST endpoints to fix the mapping for content.raw property.
297 | */
298 | $rest_post_types = get_post_types( array( 'show_in_rest' => true ) );
299 |
300 | foreach ( $rest_post_types as $post_type ) {
301 | add_filter( "rest_prepare_{$post_type}", array( $this, 'prepare_rest_post_item' ), 10, 1 );
302 | }
303 | }
304 |
305 | /**
306 | * Ensures the "raw" version of the content, typically used by Gutenberg through it's middleware pre-load / JS
307 | * hydrate process, gets handled the same as content (which runs through the `the_content` hook).
308 | *
309 | * @param WP_REST_Response $item Individual post / item in the response that is being processed.
310 | * @return WP_REST_Response Post / item with the content.raw, if present, mapped.
311 | *
312 | * @since 2.2.0
313 | */
314 | public function prepare_rest_post_item( $item = null ) {
315 | if ( isset( $item->data['content']['raw'] ) ) {
316 | $item->data['content']['raw'] = $this->map( $item->data['content']['raw'] );
317 | }
318 |
319 | return $item;
320 | }
321 |
322 | /**
323 | * Prime the settings needed for media domain mapping a particular website.
324 | *
325 | * @since 2.3.0
326 | *
327 | * @param integer $site_id Site ID to prime for media domains.
328 | * @return void
329 | */
330 | private function prime_site( $site_id = 0 ) {
331 | /**
332 | * If we have seen this website before, skip doing it again.
333 | */
334 | if ( isset( $this->sites[ $site_id ] ) ) {
335 | return;
336 | }
337 |
338 | $main_domains = $this->get_main_domains( $site_id );
339 | if ( empty( $main_domains ) ) {
340 | $this->sites[ $site_id ] = false;
341 | return;
342 | }
343 |
344 | /**
345 | * Ensure we have media domains to use.
346 | */
347 | $media_domains = DarkMatter_Domains::instance()->get_domains_by_type( DM_DOMAIN_TYPE_MEDIA, $site_id );
348 | if ( empty( $media_domains ) ) {
349 | $this->sites[ $site_id ] = false;
350 | return;
351 | }
352 |
353 | /**
354 | * Seemingly WordPress' `wp_get_attachment_url()` doesn't seem to fully work as intended for `switch_to_blog()`.
355 | * Therefore we must add the requesters' main domains in order for the map / unmap to work, as the media assets
356 | * will be served on the requesters' domains rather than the domain of the site it belongs to.
357 | */
358 | $main_domains = array_filter(
359 | array_merge(
360 | $main_domains,
361 | $this->request_main_domains
362 | )
363 | );
364 |
365 | $this->sites[ $site_id ] = [
366 | 'allowed_mimes' => $this->get_mime_types(),
367 | 'main_domains' => $main_domains,
368 | 'media_domains' => $media_domains,
369 | 'media_domains_count' => count( $media_domains ),
370 | /**
371 | * The first entry is always the unmapped.
372 | */
373 | 'unmapped' => $main_domains[0],
374 | ];
375 | }
376 |
377 | /**
378 | * Handle the `switch_to_blog()` / `restore_current_blog()` functionality.
379 | *
380 | * @since 2.3.0
381 | *
382 | * @param int $site_id Site (Blog) ID, used to retrieve the site details and Primary Domain.
383 | * @return void
384 | */
385 | public function switch_blog( $site_id = 0 ) {
386 | $this->current_site_id = ( ! empty( $site_id ) ? intval( $site_id ) : get_current_blog_id() );
387 | $this->prime_site( $this->current_site_id );
388 | }
389 |
390 | /**
391 | * Used to unmap Media domains.
392 | *
393 | * @param string $value Value that may contain Media domains.
394 | * @return string Value with the Media domains removed and replaced with the unmapped domain.
395 | *
396 | * @since 2.2.0
397 | */
398 | public function unmap( $value = '' ) {
399 | if ( ! $this->can_map() || empty( $value ) ) {
400 | return $value;
401 | }
402 |
403 | /**
404 | * Create an array of strings of the Media domains.
405 | */
406 | $media_domains = wp_list_pluck( $this->sites[ $this->current_site_id ]['media_domains'], 'domain' );
407 |
408 | /**
409 | * Ensure we have domains that are to be unmapped.
410 | */
411 | if ( empty( $media_domains ) ) {
412 | return $value;
413 | }
414 |
415 | /**
416 | * Replace the Media domains with the unmapped domain.
417 | */
418 | return preg_replace(
419 | '#://(' . implode( '|', $media_domains ) . ')#',
420 | '://' . $this->sites[ $this->current_site_id ]['unmapped'],
421 | $value
422 | );
423 | }
424 |
425 | /**
426 | * Return the Singleton Instance of the class.
427 | *
428 | * @return DM_Media
429 | *
430 | * @since 2.2.0
431 | */
432 | public static function instance() {
433 | static $instance = false;
434 |
435 | if ( ! $instance ) {
436 | $instance = new self();
437 | }
438 |
439 | return $instance;
440 | }
441 | }
442 | DM_Media::instance();
443 |
--------------------------------------------------------------------------------
/domain-mapping/cli/class-darkmatter-domain-cli.php:
--------------------------------------------------------------------------------
1 |
25 | * : The domain you wish to add.
26 | *
27 | * [--disable]
28 | * : Allows you to add a domain, primary or secondary, to the Site without
29 | * it being used immediately.
30 | *
31 | * [--force]
32 | * : Force Dark Matter to add the domain. This is required if you wish to
33 | * remove a Primary domain from a Site.
34 | *
35 | * [--https]
36 | * : Sets the protocol to be HTTPS. This is only needed when used with the --primary flag and is ignored otherwise.
37 | *
38 | * [--primary]
39 | * : Sets the domain to be the primary domain for the Site, the one which visitors will be redirected to.
40 | *
41 | * [--secondary]
42 | * : Sets the domain to be a secondary domain for the Site. Visitors will be redirected from this domain to the primary.
43 | *
44 | * [--type]
45 | * : Choose the type of domain. Useful for creating "media" domains. Defaults to "main".
46 | *
47 | * ### EXAMPLES
48 | * Set the primary domain and set the protocol to HTTPS.
49 | *
50 | * wp --url="sites.my.com/siteone" darkmatter domain add www.primarydomain.com --primary --https
51 | *
52 | * Set a media domain for a site.
53 | *
54 | * wp --url="sites.my.com/sitefifteen" darkmatter domain add fifteen.mycdn.com --type=media
55 | *
56 | * @since 2.0.0
57 | *
58 | * @param array $args CLI args.
59 | * @param array $assoc_args CLI args maintaining the flag names from the terminal.
60 | */
61 | public function add( $args, $assoc_args ) {
62 | if ( empty( $args[0] ) ) {
63 | WP_CLI::error( __( 'Please include a fully qualified domain name to be added.', 'dark-matter' ) );
64 | }
65 |
66 | $fqdn = $args[0];
67 |
68 | $opts = wp_parse_args(
69 | $assoc_args,
70 | [
71 | 'disable' => false,
72 | 'force' => false,
73 | 'https' => true,
74 | 'primary' => false,
75 | 'type' => 'main',
76 | ]
77 | );
78 |
79 | $type = $this->check_type_opt( $opts['type'] );
80 |
81 | /**
82 | * Add the domain.
83 | */
84 | $db = DarkMatter_Domains::instance();
85 | $result = $db->add( $fqdn, $opts['primary'], $opts['https'], $opts['force'], ! $opts['disable'], $type );
86 |
87 | if ( is_wp_error( $result ) ) {
88 | $error_msg = $result->get_error_message();
89 |
90 | if ( 'primary' === $result->get_error_code() ) {
91 | $error_msg = __( 'You cannot add this domain as the primary domain without using the --force flag.', 'dark-matter' );
92 | }
93 |
94 | WP_CLI::error( $error_msg );
95 | }
96 |
97 | WP_CLI::success( $fqdn . __( ': was added.', 'dark-matter' ) );
98 | }
99 |
100 | /**
101 | * Checks to ensure the value of type is valid and useable.
102 | *
103 | * @param string $type Type value to be checked.
104 | * @return integer Domain type.
105 | *
106 | * @since 2.2.0
107 | */
108 | private function check_type_opt( $type = '' ) {
109 | /**
110 | * Handle the Media flag.
111 | */
112 | $domain_types = [
113 | 'main' => DM_DOMAIN_TYPE_MAIN,
114 | 'media' => DM_DOMAIN_TYPE_MEDIA,
115 | ];
116 |
117 | if ( array_key_exists( strtolower( $type ), $domain_types ) ) {
118 | return $domain_types[ $type ];
119 | }
120 |
121 | return DM_DOMAIN_TYPE_MAIN;
122 | }
123 |
124 | /**
125 | * List a domain for the current Site. If the the URL is omitted and the
126 | * command is run on the root Site, it will list all domains available for
127 | * the whole network.
128 | *
129 | * ### OPTIONS
130 | *
131 | * [--format]
132 | * : Determine which format that should be returned. Defaults to "table" and
133 | * accepts "json", "csv", "yaml", and "count".
134 | *
135 | * [--primary]
136 | * : Filter the results to return only the Primary domains. This will ignore
137 | * the --url parameter.
138 | *
139 | * ### EXAMPLES
140 | * List all domains for a specific Site.
141 | *
142 | * wp --url="sites.my.com/siteone" darkmatter domain list
143 | *
144 | * Get all domains for a specific Site in JSON format.
145 | *
146 | * wp --url="sites.my.com/siteone" darkmatter domain list --format=json
147 | *
148 | * List all domains for all Sites.
149 | *
150 | * wp darkmatter domain list
151 | *
152 | * @since 2.0.0
153 | *
154 | * @param array $args CLI args.
155 | * @param array $assoc_args CLI args maintaining the flag names from the terminal.
156 | */
157 | public function list( $args, $assoc_args ) {
158 | /**
159 | * Handle and validate the format flag if provided.
160 | */
161 | $opts = wp_parse_args(
162 | $assoc_args,
163 | [
164 | 'format' => 'table',
165 | 'primary' => false,
166 | ]
167 | );
168 |
169 | if ( ! in_array( $opts['format'], array( 'table', 'json', 'csv', 'yaml', 'count' ) ) ) {
170 | $opts['format'] = 'table';
171 | }
172 |
173 | if ( $opts['primary'] ) {
174 | $db = DarkMatter_Primary::instance();
175 | $domains = $db->get_all();
176 | } else {
177 | /**
178 | * Retrieve the current Blog ID. However this will be set to null if
179 | * this is the root Site to retrieve all domains.
180 | */
181 | $site_id = get_current_blog_id();
182 |
183 | if ( is_main_site() ) {
184 | $site_id = null;
185 | }
186 |
187 | $db = DarkMatter_Domains::instance();
188 | $domains = $db->get_domains( $site_id );
189 | }
190 |
191 | /**
192 | * Filter out and format the columns and values appropriately.
193 | */
194 | $domains = array_map(
195 | function ( $domain ) {
196 | $no_val = __( 'No', 'dark-matter' );
197 | $yes_val = __( 'Yes', 'dark-matter' );
198 |
199 | $columns = array(
200 | 'F.Q.D.N.' => $domain->domain,
201 | 'Primary' => ( $domain->is_primary ? $yes_val : $no_val ),
202 | 'Protocol' => ( $domain->is_https ? 'HTTPS' : 'HTTP' ),
203 | 'Active' => ( $domain->active ? $yes_val : $no_val ),
204 | 'Type' => ( DM_DOMAIN_TYPE_MEDIA === $domain->type ? 'Media' : 'Main' ),
205 | );
206 |
207 | /**
208 | * If the query is the root Site and we are displaying all domains,
209 | * then we retrieve and include the Site Name.
210 | */
211 | $site = get_site( $domain->blog_id );
212 |
213 | if ( empty( $site ) ) {
214 | $columns['Site'] = __( 'Unknown.', 'dark-matter' );
215 | } else {
216 | $columns['Site'] = $site->blogname;
217 | }
218 |
219 | return $columns;
220 | },
221 | $domains
222 | );
223 |
224 | /**
225 | * Determine which headers to use for the Display.
226 | */
227 | $display = [
228 | 'F.Q.D.N.',
229 | 'Primary',
230 | 'Protocol',
231 | 'Active',
232 | 'Type',
233 | ];
234 |
235 | if ( is_main_site() ) {
236 | $display = [
237 | 'F.Q.D.N.',
238 | 'Site',
239 | 'Primary',
240 | 'Protocol',
241 | 'Active',
242 | 'Type',
243 | ];
244 | }
245 |
246 | WP_CLI\Utils\format_items( $opts['format'], $domains, $display );
247 | }
248 |
249 | /**
250 | * Remove a specific domain on a Site on the WordPress Network.
251 | *
252 | * ### OPTIONS
253 | *
254 | *
255 | * : The domain you wish to remove.
256 | *
257 | * [--force]
258 | * : Force Dark Matter to remove the domain. This is required if you wish to
259 | * remove a Primary domain from a Site.
260 | *
261 | * ### EXAMPLES
262 | * Remove a domain from a Site.
263 | *
264 | * wp --url="sites.my.com/siteone" darkmatter domain remove www.primarydomain.com
265 | *
266 | * Remove a primary domain from a Site. Please note; this ***WILL NOT*** set
267 | * another domain to replace the Primary. You must set this using either the
268 | * add or set commands.
269 | *
270 | * wp --url="sites.my.com/siteone" darkmatter domain remove www.primarydomain.com --force
271 | *
272 | * @since 2.0.0
273 | *
274 | * @param array $args CLI args.
275 | * @param array $assoc_args CLI args maintaining the flag names from the terminal.
276 | */
277 | public function remove( $args, $assoc_args ) {
278 | if ( empty( $args[0] ) ) {
279 | WP_CLI::error( __( 'Please include a fully qualified domain name to be removed.', 'dark-matter' ) );
280 | }
281 |
282 | $fqdn = $args[0];
283 |
284 | $opts = wp_parse_args(
285 | $assoc_args,
286 | [
287 | 'force' => false,
288 | ]
289 | );
290 |
291 | $db = DarkMatter_Domains::instance();
292 |
293 | /**
294 | * Remove the domain.
295 | */
296 | $result = $db->delete( $fqdn, $opts['force'] );
297 |
298 | if ( is_wp_error( $result ) ) {
299 | $error_msg = $result->get_error_message();
300 |
301 | if ( 'primary' === $result->get_error_code() ) {
302 | $error_msg = __( 'You cannot delete a primary domain. Use --force flag if you really want to and know what you are doing.', 'dark-matter' );
303 | }
304 |
305 | WP_CLI::error( $error_msg );
306 | }
307 |
308 | WP_CLI::success( $fqdn . __( ': has been removed.', 'dark-matter' ) );
309 | }
310 |
311 | /**
312 | * Update the flags for a specific domain on a Site on the WordPress Network.
313 | *
314 | * ### OPTIONS
315 | *
316 | *
317 | * : The domain you wish to update.
318 | *
319 | * [--enable]
320 | * : Enable the domain on the Site.
321 | *
322 | * [--disable]
323 | * : Disable the domain on the Site.
324 | *
325 | * [--force]
326 | * : Force Dark Matter to update the domain.
327 | *
328 | * [--use-http]
329 | * : Set the protocol to be HTTP.
330 | *
331 | * [--use-https]
332 | * : Set the protocol to be HTTPS.
333 | *
334 | * [--primary]
335 | * : Set the domain to be the primary domain for the Site, the one which
336 | * visitors will be redirected to. If a primary domain is already set, then
337 | * you must use the --force flag to perform the update.
338 | *
339 | * [--secondary]
340 | * : Set the domain to be a secondary domain for the Site. Visitors will be
341 | * redirected from this domain to the primary.
342 | *
343 | * [--type]
344 | * : Choose the type of domain. Useful for creating "media" domains. Defaults to "main".
345 | *
346 | * ### EXAMPLES
347 | * Set the primary domain and set the protocol to HTTPS.
348 | *
349 | * wp --url="sites.my.com/siteone" darkmatter domain set www.primarydomain.com --primary
350 | * wp --url="sites.my.com/siteone" darkmatter domain set www.secondarydomain.com --secondary
351 | *
352 | * Convert a secondary domain into a media domain. Useful for when repurposing an old domain for use a CDN for media
353 | * assets.
354 | *
355 | * wp --url="sites.my.com/siteone" darkmatter domain set www.secondarydomain.com --type=media
356 | *
357 | * Convert a Media domain to a main domain. This is useful in scenarios when a media domain is redundant and to
358 | * ensure it redirects to the website.
359 | *
360 | * wp --url="sites.my.com/siteone" darkmatter domain set one.mycdntest.com --type=main --secondary
361 | *
362 | * @since 2.0.0
363 | *
364 | * @param array $args CLI args.
365 | * @param array $assoc_args CLI args maintaining the flag names from the terminal.
366 | */
367 | public function set( $args, $assoc_args ) {
368 | if ( empty( $args[0] ) ) {
369 | WP_CLI::error( __( 'Please include a fully qualified domain name to be removed.', 'dark-matter' ) );
370 | }
371 |
372 | $fqdn = $args[0];
373 |
374 | $db = DarkMatter_Domains::instance();
375 | $domain_before = $db->get( $fqdn );
376 |
377 | $opts = wp_parse_args(
378 | $assoc_args,
379 | [
380 | 'disable' => false,
381 | 'enable' => false,
382 | 'force' => false,
383 | 'use-http' => null,
384 | 'use-https' => true,
385 | 'primary' => null,
386 | 'secondary' => null,
387 | ]
388 | );
389 |
390 | /**
391 | * Ensure that contradicting options are not being supplied.
392 | */
393 | if ( $opts['use-http'] && $opts['use-https'] ) {
394 | WP_CLI::error( __( 'A domain cannot be both HTTP and HTTPS.', 'dark-matter' ) );
395 | }
396 |
397 | if ( $opts['primary'] && $opts['secondary'] ) {
398 | WP_CLI::error( __( 'A domain cannot be both primary and secondary.', 'dark-matter' ) );
399 | }
400 |
401 | if ( $opts['enable'] && $opts['disable'] ) {
402 | WP_CLI::error( __( 'A domain cannot be both enabled and disabled.', 'dark-matter' ) );
403 | }
404 |
405 | /**
406 | * Determine if we are switching between HTTP and HTTPS.
407 | */
408 | $is_https = $opts['use-https'];
409 |
410 | if ( $opts['use-http'] ) {
411 | $is_https = false;
412 | }
413 |
414 | /**
415 | * Determine if we are switching between primary and secondary.
416 | */
417 | $is_primary = $opts['primary'];
418 |
419 | if ( $opts['secondary'] ) {
420 | $is_primary = false;
421 | }
422 |
423 | /**
424 | * Determine if we are switching between enabled and disabled.
425 | */
426 | $active = $domain_before->active;
427 |
428 | if ( $opts['enable'] ) {
429 | $active = true;
430 | }
431 |
432 | if ( $opts['disable'] ) {
433 | $active = false;
434 | }
435 |
436 | /**
437 | * If the type is specified, then validate it to ensure it is correct.
438 | */
439 | $type = null;
440 | if ( ! empty( $opts['type'] ) ) {
441 | $type = $this->check_type_opt( $opts['type'] );
442 | }
443 |
444 | /**
445 | * Update the records.
446 | */
447 | $result = $db->update( $fqdn, $is_primary, $is_https, $opts['force'], $active, $type );
448 |
449 | /**
450 | * Handle the output for errors and success.
451 | */
452 | if ( is_wp_error( $result ) ) {
453 | $error_msg = $result->get_error_message();
454 |
455 | if ( 'primary' === $result->get_error_code() ) {
456 | $error_msg = __( 'You cannot modify the primary domain. Use --force flag if you really want to and know what you are doing.', 'dark-matter' );
457 | }
458 |
459 | WP_CLI::error( $error_msg );
460 | }
461 |
462 | WP_CLI::success( $fqdn . __( ': successfully updated.', 'dark-matter' ) );
463 | }
464 | }
465 | WP_CLI::add_command( 'darkmatter domain', 'DarkMatter_Domain_CLI' );
466 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Dark Matter - WordPress domain mapping plugin
2 |
3 | Copyright 2022 by Cameron Terry
4 |
5 | This program is free software; you can redistribute it and/or modify
6 | it under the terms of the GNU General Public License as published by
7 | the Free Software Foundation; either version 2 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU General Public License for more details.
14 |
15 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
16 |
17 | GNU GENERAL PUBLIC LICENSE
18 | Version 2, June 1991
19 |
20 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
21 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 | Everyone is permitted to copy and distribute verbatim copies
23 | of this license document, but changing it is not allowed.
24 |
25 | Preamble
26 |
27 | The licenses for most software are designed to take away your
28 | freedom to share and change it. By contrast, the GNU General Public
29 | License is intended to guarantee your freedom to share and change free
30 | software--to make sure the software is free for all its users. This
31 | General Public License applies to most of the Free Software
32 | Foundation's software and to any other program whose authors commit to
33 | using it. (Some other Free Software Foundation software is covered by
34 | the GNU Lesser General Public License instead.) You can apply it to
35 | your programs, too.
36 |
37 | When we speak of free software, we are referring to freedom, not
38 | price. Our General Public Licenses are designed to make sure that you
39 | have the freedom to distribute copies of free software (and charge for
40 | this service if you wish), that you receive source code or can get it
41 | if you want it, that you can change the software or use pieces of it
42 | in new free programs; and that you know you can do these things.
43 |
44 | To protect your rights, we need to make restrictions that forbid
45 | anyone to deny you these rights or to ask you to surrender the rights.
46 | These restrictions translate to certain responsibilities for you if you
47 | distribute copies of the software, or if you modify it.
48 |
49 | For example, if you distribute copies of such a program, whether
50 | gratis or for a fee, you must give the recipients all the rights that
51 | you have. You must make sure that they, too, receive or can get the
52 | source code. And you must show them these terms so they know their
53 | rights.
54 |
55 | We protect your rights with two steps: (1) copyright the software, and
56 | (2) offer you this license which gives you legal permission to copy,
57 | distribute and/or modify the software.
58 |
59 | Also, for each author's protection and ours, we want to make certain
60 | that everyone understands that there is no warranty for this free
61 | software. If the software is modified by someone else and passed on, we
62 | want its recipients to know that what they have is not the original, so
63 | that any problems introduced by others will not reflect on the original
64 | authors' reputations.
65 |
66 | Finally, any free program is threatened constantly by software
67 | patents. We wish to avoid the danger that redistributors of a free
68 | program will individually obtain patent licenses, in effect making the
69 | program proprietary. To prevent this, we have made it clear that any
70 | patent must be licensed for everyone's free use or not licensed at all.
71 |
72 | The precise terms and conditions for copying, distribution and
73 | modification follow.
74 |
75 | GNU GENERAL PUBLIC LICENSE
76 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
77 |
78 | 0. This License applies to any program or other work which contains
79 | a notice placed by the copyright holder saying it may be distributed
80 | under the terms of this General Public License. The "Program", below,
81 | refers to any such program or work, and a "work based on the Program"
82 | means either the Program or any derivative work under copyright law:
83 | that is to say, a work containing the Program or a portion of it,
84 | either verbatim or with modifications and/or translated into another
85 | language. (Hereinafter, translation is included without limitation in
86 | the term "modification".) Each licensee is addressed as "you".
87 |
88 | Activities other than copying, distribution and modification are not
89 | covered by this License; they are outside its scope. The act of
90 | running the Program is not restricted, and the output from the Program
91 | is covered only if its contents constitute a work based on the
92 | Program (independent of having been made by running the Program).
93 | Whether that is true depends on what the Program does.
94 |
95 | 1. You may copy and distribute verbatim copies of the Program's
96 | source code as you receive it, in any medium, provided that you
97 | conspicuously and appropriately publish on each copy an appropriate
98 | copyright notice and disclaimer of warranty; keep intact all the
99 | notices that refer to this License and to the absence of any warranty;
100 | and give any other recipients of the Program a copy of this License
101 | along with the Program.
102 |
103 | You may charge a fee for the physical act of transferring a copy, and
104 | you may at your option offer warranty protection in exchange for a fee.
105 |
106 | 2. You may modify your copy or copies of the Program or any portion
107 | of it, thus forming a work based on the Program, and copy and
108 | distribute such modifications or work under the terms of Section 1
109 | above, provided that you also meet all of these conditions:
110 |
111 | a) You must cause the modified files to carry prominent notices
112 | stating that you changed the files and the date of any change.
113 |
114 | b) You must cause any work that you distribute or publish, that in
115 | whole or in part contains or is derived from the Program or any
116 | part thereof, to be licensed as a whole at no charge to all third
117 | parties under the terms of this License.
118 |
119 | c) If the modified program normally reads commands interactively
120 | when run, you must cause it, when started running for such
121 | interactive use in the most ordinary way, to print or display an
122 | announcement including an appropriate copyright notice and a
123 | notice that there is no warranty (or else, saying that you provide
124 | a warranty) and that users may redistribute the program under
125 | these conditions, and telling the user how to view a copy of this
126 | License. (Exception: if the Program itself is interactive but
127 | does not normally print such an announcement, your work based on
128 | the Program is not required to print an announcement.)
129 |
130 | These requirements apply to the modified work as a whole. If
131 | identifiable sections of that work are not derived from the Program,
132 | and can be reasonably considered independent and separate works in
133 | themselves, then this License, and its terms, do not apply to those
134 | sections when you distribute them as separate works. But when you
135 | distribute the same sections as part of a whole which is a work based
136 | on the Program, the distribution of the whole must be on the terms of
137 | this License, whose permissions for other licensees extend to the
138 | entire whole, and thus to each and every part regardless of who wrote it.
139 |
140 | Thus, it is not the intent of this section to claim rights or contest
141 | your rights to work written entirely by you; rather, the intent is to
142 | exercise the right to control the distribution of derivative or
143 | collective works based on the Program.
144 |
145 | In addition, mere aggregation of another work not based on the Program
146 | with the Program (or with a work based on the Program) on a volume of
147 | a storage or distribution medium does not bring the other work under
148 | the scope of this License.
149 |
150 | 3. You may copy and distribute the Program (or a work based on it,
151 | under Section 2) in object code or executable form under the terms of
152 | Sections 1 and 2 above provided that you also do one of the following:
153 |
154 | a) Accompany it with the complete corresponding machine-readable
155 | source code, which must be distributed under the terms of Sections
156 | 1 and 2 above on a medium customarily used for software interchange; or,
157 |
158 | b) Accompany it with a written offer, valid for at least three
159 | years, to give any third party, for a charge no more than your
160 | cost of physically performing source distribution, a complete
161 | machine-readable copy of the corresponding source code, to be
162 | distributed under the terms of Sections 1 and 2 above on a medium
163 | customarily used for software interchange; or,
164 |
165 | c) Accompany it with the information you received as to the offer
166 | to distribute corresponding source code. (This alternative is
167 | allowed only for noncommercial distribution and only if you
168 | received the program in object code or executable form with such
169 | an offer, in accord with Subsection b above.)
170 |
171 | The source code for a work means the preferred form of the work for
172 | making modifications to it. For an executable work, complete source
173 | code means all the source code for all modules it contains, plus any
174 | associated interface definition files, plus the scripts used to
175 | control compilation and installation of the executable. However, as a
176 | special exception, the source code distributed need not include
177 | anything that is normally distributed (in either source or binary
178 | form) with the major components (compiler, kernel, and so on) of the
179 | operating system on which the executable runs, unless that component
180 | itself accompanies the executable.
181 |
182 | If distribution of executable or object code is made by offering
183 | access to copy from a designated place, then offering equivalent
184 | access to copy the source code from the same place counts as
185 | distribution of the source code, even though third parties are not
186 | compelled to copy the source along with the object code.
187 |
188 | 4. You may not copy, modify, sublicense, or distribute the Program
189 | except as expressly provided under this License. Any attempt
190 | otherwise to copy, modify, sublicense or distribute the Program is
191 | void, and will automatically terminate your rights under this License.
192 | However, parties who have received copies, or rights, from you under
193 | this License will not have their licenses terminated so long as such
194 | parties remain in full compliance.
195 |
196 | 5. You are not required to accept this License, since you have not
197 | signed it. However, nothing else grants you permission to modify or
198 | distribute the Program or its derivative works. These actions are
199 | prohibited by law if you do not accept this License. Therefore, by
200 | modifying or distributing the Program (or any work based on the
201 | Program), you indicate your acceptance of this License to do so, and
202 | all its terms and conditions for copying, distributing or modifying
203 | the Program or works based on it.
204 |
205 | 6. Each time you redistribute the Program (or any work based on the
206 | Program), the recipient automatically receives a license from the
207 | original licensor to copy, distribute or modify the Program subject to
208 | these terms and conditions. You may not impose any further
209 | restrictions on the recipients' exercise of the rights granted herein.
210 | You are not responsible for enforcing compliance by third parties to
211 | this License.
212 |
213 | 7. If, as a consequence of a court judgment or allegation of patent
214 | infringement or for any other reason (not limited to patent issues),
215 | conditions are imposed on you (whether by court order, agreement or
216 | otherwise) that contradict the conditions of this License, they do not
217 | excuse you from the conditions of this License. If you cannot
218 | distribute so as to satisfy simultaneously your obligations under this
219 | License and any other pertinent obligations, then as a consequence you
220 | may not distribute the Program at all. For example, if a patent
221 | license would not permit royalty-free redistribution of the Program by
222 | all those who receive copies directly or indirectly through you, then
223 | the only way you could satisfy both it and this License would be to
224 | refrain entirely from distribution of the Program.
225 |
226 | If any portion of this section is held invalid or unenforceable under
227 | any particular circumstance, the balance of the section is intended to
228 | apply and the section as a whole is intended to apply in other
229 | circumstances.
230 |
231 | It is not the purpose of this section to induce you to infringe any
232 | patents or other property right claims or to contest validity of any
233 | such claims; this section has the sole purpose of protecting the
234 | integrity of the free software distribution system, which is
235 | implemented by public license practices. Many people have made
236 | generous contributions to the wide range of software distributed
237 | through that system in reliance on consistent application of that
238 | system; it is up to the author/donor to decide if he or she is willing
239 | to distribute software through any other system and a licensee cannot
240 | impose that choice.
241 |
242 | This section is intended to make thoroughly clear what is believed to
243 | be a consequence of the rest of this License.
244 |
245 | 8. If the distribution and/or use of the Program is restricted in
246 | certain countries either by patents or by copyrighted interfaces, the
247 | original copyright holder who places the Program under this License
248 | may add an explicit geographical distribution limitation excluding
249 | those countries, so that distribution is permitted only in or among
250 | countries not thus excluded. In such case, this License incorporates
251 | the limitation as if written in the body of this License.
252 |
253 | 9. The Free Software Foundation may publish revised and/or new versions
254 | of the General Public License from time to time. Such new versions will
255 | be similar in spirit to the present version, but may differ in detail to
256 | address new problems or concerns.
257 |
258 | Each version is given a distinguishing version number. If the Program
259 | specifies a version number of this License which applies to it and "any
260 | later version", you have the option of following the terms and conditions
261 | either of that version or of any later version published by the Free
262 | Software Foundation. If the Program does not specify a version number of
263 | this License, you may choose any version ever published by the Free Software
264 | Foundation.
265 |
266 | 10. If you wish to incorporate parts of the Program into other free
267 | programs whose distribution conditions are different, write to the author
268 | to ask for permission. For software which is copyrighted by the Free
269 | Software Foundation, write to the Free Software Foundation; we sometimes
270 | make exceptions for this. Our decision will be guided by the two goals
271 | of preserving the free status of all derivatives of our free software and
272 | of promoting the sharing and reuse of software generally.
273 |
274 | NO WARRANTY
275 |
276 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
277 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
278 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
279 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
280 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
281 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
282 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
283 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
284 | REPAIR OR CORRECTION.
285 |
286 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
287 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
288 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
289 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
290 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
291 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
292 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
293 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
294 | POSSIBILITY OF SUCH DAMAGES.
295 |
296 | END OF TERMS AND CONDITIONS
297 |
298 | How to Apply These Terms to Your New Programs
299 |
300 | If you develop a new program, and you want it to be of the greatest
301 | possible use to the public, the best way to achieve this is to make it
302 | free software which everyone can redistribute and change under these terms.
303 |
304 | To do so, attach the following notices to the program. It is safest
305 | to attach them to the start of each source file to most effectively
306 | convey the exclusion of warranty; and each file should have at least
307 | the "copyright" line and a pointer to where the full notice is found.
308 |
309 | {description}
310 | Copyright (C) {year} {fullname}
311 |
312 | This program is free software; you can redistribute it and/or modify
313 | it under the terms of the GNU General Public License as published by
314 | the Free Software Foundation; either version 2 of the License, or
315 | (at your option) any later version.
316 |
317 | This program is distributed in the hope that it will be useful,
318 | but WITHOUT ANY WARRANTY; without even the implied warranty of
319 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
320 | GNU General Public License for more details.
321 |
322 | You should have received a copy of the GNU General Public License along
323 | with this program; if not, write to the Free Software Foundation, Inc.,
324 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
325 |
326 | Also add information on how to contact you by electronic and paper mail.
327 |
328 | If the program is interactive, make it output a short notice like this
329 | when it starts in an interactive mode:
330 |
331 | Gnomovision version 69, Copyright (C) year name of author
332 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
333 | This is free software, and you are welcome to redistribute it
334 | under certain conditions; type `show c' for details.
335 |
336 | The hypothetical commands `show w' and `show c' should show the appropriate
337 | parts of the General Public License. Of course, the commands you use may
338 | be called something other than `show w' and `show c'; they could even be
339 | mouse-clicks or menu items--whatever suits your program.
340 |
341 | You should also get your employer (if you work as a programmer) or your
342 | school, if any, to sign a "copyright disclaimer" for the program, if
343 | necessary. Here is a sample; alter the names:
344 |
345 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
346 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
347 |
348 | {signature of Ty Coon}, 1 April 1989
349 | Ty Coon, President of Vice
350 |
351 | This General Public License does not permit incorporating your program into
352 | proprietary programs. If your program is a subroutine library, you may
353 | consider it more useful to permit linking proprietary applications with the
354 | library. If this is what you want to do, use the GNU Lesser General
355 | Public License instead of this License.
356 |
--------------------------------------------------------------------------------
/domain-mapping/rest/class-dm-rest-domains-controller.php:
--------------------------------------------------------------------------------
1 | namespace = 'dm/v1';
22 | $this->rest_base = 'domain';
23 | $this->rest_base_plural = 'domains';
24 | }
25 |
26 | /**
27 | * Add a domain to the Site.
28 | *
29 | * @since 2.0.0
30 | *
31 | * @param WP_REST_Request $request Current request.
32 | * @return WP_REST_Response|mixed WP_REST_Response on success. WP_Error on failure.
33 | */
34 | public function create_item( $request ) {
35 | $db = DarkMatter_Domains::instance();
36 |
37 | $item = $this->prepare_item_for_database( $request );
38 |
39 | $result = $db->add( $item['domain'], $item['is_primary'], $item['is_https'], $request['force'], $item['is_active'] );
40 |
41 | /**
42 | * Return errors as-is. This is maintain consistency and parity with the
43 | * WP CLI commands.
44 | */
45 | if ( is_wp_error( $result ) ) {
46 | return rest_ensure_response( $result );
47 | }
48 |
49 | /**
50 | * Prepare response for successfully adding a domain.
51 | */
52 | $response = rest_ensure_response(
53 | $this->prepare_item_for_response( $result, $request )
54 | );
55 |
56 | $response->set_status( 201 );
57 | $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $result->domain ) ) );
58 |
59 | return $response;
60 | }
61 |
62 | /**
63 | * Checks if a given request has access to add a domain.
64 | *
65 | * @since 2.0.0
66 | *
67 | * @param WP_REST_Request $request Current request.
68 | * @return boolean True if the current user is a Super Admin. False otherwise.
69 | */
70 | public function create_item_permissions_check( $request ) {
71 | /** This action is documented in domain-mapping/classes/class-dm-ui.php */
72 | return current_user_can( apply_filters( 'dark_matter_domain_permission', 'upgrade_network', 'rest-create' ) );
73 | }
74 |
75 | /**
76 | * Delete a domain.
77 | *
78 | * @since 2.0.0
79 | *
80 | * @param WP_REST_Request $request Current request.
81 | * @return WP_REST_Response|mixed WP_REST_Response on success. WP_Error on failure.
82 | */
83 | public function delete_item( $request ) {
84 | $db = DarkMatter_Domains::instance();
85 |
86 | $result = $db->delete( $request['domain'], $request['force'] );
87 |
88 | /**
89 | * Return errors as-is. This is maintain consistency and parity with the
90 | * WP CLI commands.
91 | */
92 | if ( is_wp_error( $result ) ) {
93 | return rest_ensure_response( $result );
94 | }
95 |
96 | /**
97 | * Handle the response for the REST endpoint.
98 | */
99 | $response = rest_ensure_response(
100 | array(
101 | 'deleted' => true,
102 | 'domain' => $request['domain'],
103 | )
104 | );
105 |
106 | return $response;
107 | }
108 |
109 | /**
110 | * Checks if a given request has access to delete a domain.
111 | *
112 | * @since 2.0.0
113 | *
114 | * @param WP_REST_Request $request Current request.
115 | * @return boolean True if the current user is a Super Admin. False otherwise.
116 | */
117 | public function delete_item_permissions_check( $request ) {
118 | /** This action is documented in domain-mapping/classes/class-dm-ui.php */
119 | return current_user_can( apply_filters( 'dark_matter_domain_permission', 'upgrade_network', 'rest-delete' ) );
120 | }
121 |
122 | /**
123 | * Return the Restricted domains as a list in REST response.
124 | *
125 | * @since 2.0.0
126 | *
127 | * @param WP_REST_Request $request Current request.
128 | * @return WP_REST_Response|mixed WP_REST_Response on success. WP_Error on failure.
129 | */
130 | public function get_item( $request ) {
131 | $db = DarkMatter_Domains::instance();
132 |
133 | $result = $db->get( $request['domain'] );
134 |
135 | /**
136 | * Return errors as-is. This is maintain consistency and parity with the
137 | * WP CLI commands.
138 | */
139 | if ( is_wp_error( $result ) ) {
140 | return rest_ensure_response( $result );
141 | }
142 |
143 | /**
144 | * Handle the response for the REST endpoint.
145 | */
146 | $response = $this->prepare_item_for_response( $result, $request );
147 |
148 | return rest_ensure_response( $response );
149 | }
150 |
151 | /**
152 | * JSON Schema definition for Domain.
153 | *
154 | * @since 2.0.0
155 | *
156 | * @return array JSON Schema definition.
157 | */
158 | public function get_item_schema() {
159 | $schema = array(
160 | '$schema' => 'http://json-schema.org/draft-04/schema#',
161 | 'title' => 'Domain',
162 | 'type' => 'object',
163 | 'properties' => array(
164 | 'id' => array(
165 | 'context' => array( 'view', 'edit' ),
166 | 'description' => __( 'Unique identifier for the object.', 'dark-matter' ),
167 | 'readonly' => true,
168 | 'type' => 'integer',
169 | ),
170 | 'domain' => array(
171 | 'context' => array( 'view', 'edit' ),
172 | 'default' => '',
173 | 'description' => __( 'Domain name.', 'dark-matter' ),
174 | 'required' => true,
175 | 'type' => 'string',
176 | ),
177 | 'is_primary' => array(
178 | 'context' => array( 'view', 'edit' ),
179 | 'default' => null,
180 | 'description' => __( 'Domain is the primary domain for the Site.', 'dark-matter' ),
181 | 'required' => false,
182 | 'type' => 'boolean',
183 | ),
184 | 'is_active' => array(
185 | 'context' => array( 'view', 'edit' ),
186 | 'default' => null,
187 | 'description' => __( 'Domain is currently being used.', 'dark-matter' ),
188 | 'required' => false,
189 | 'type' => 'boolean',
190 | ),
191 | 'is_https' => array(
192 | 'context' => array( 'view', 'edit' ),
193 | 'default' => null,
194 | 'description' => __( 'Domain is to be available on the HTTPS protocol.', 'dark-matter' ),
195 | 'required' => false,
196 | 'type' => 'boolean',
197 | ),
198 | 'type' => array(
199 | 'context' => array( 'view', 'edit' ),
200 | 'default' => null,
201 | 'description' => __( 'Type of domain.', 'dark-matter' ),
202 | 'required' => false,
203 | 'type' => 'integer',
204 | ),
205 | 'site' => array(
206 | 'description' => __( 'Site ID the domain is assigned against.', 'dark-matter' ),
207 | 'type' => 'object',
208 | 'context' => array( 'view', 'edit' ),
209 | 'readonly' => true,
210 | 'properties' => array(
211 | 'blog_id' => array(
212 | 'context' => array( 'view', 'edit' ),
213 | 'description' => __( 'Site ID.', 'dark-matter' ),
214 | 'readonly' => true,
215 | 'required' => false,
216 | 'type' => 'integer',
217 | ),
218 | 'site_id' => array(
219 | 'context' => array( 'view', 'edit' ),
220 | 'description' => __( 'The ID of the site\'s parent network.', 'dark-matter' ),
221 | 'readonly' => true,
222 | 'required' => false,
223 | 'type' => 'integer',
224 | ),
225 | 'domain' => array(
226 | 'context' => array( 'view', 'edit' ),
227 | 'description' => __( 'Domain of the site.', 'dark-matter' ),
228 | 'readonly' => true,
229 | 'required' => false,
230 | 'type' => 'string',
231 | ),
232 | 'path' => array(
233 | 'context' => array( 'view', 'edit' ),
234 | 'description' => __( 'Path of the site.', 'dark-matter' ),
235 | 'readonly' => true,
236 | 'required' => false,
237 | 'type' => 'string',
238 | ),
239 | 'registered' => array(
240 | 'context' => array( 'view', 'edit' ),
241 | 'description' => __( 'The date on which the site was created or registered.', 'dark-matter' ),
242 | 'format' => 'date-time',
243 | 'readonly' => true,
244 | 'required' => false,
245 | 'type' => 'string',
246 | ),
247 | 'last_updated' => array(
248 | 'context' => array( 'view', 'edit' ),
249 | 'description' => __( 'The date and time on which site settings were last updated.', 'dark-matter' ),
250 | 'format' => 'date-time',
251 | 'readonly' => true,
252 | 'required' => false,
253 | 'type' => 'string',
254 | ),
255 | 'public' => array(
256 | 'context' => array( 'view', 'edit' ),
257 | 'description' => __( 'Whether the site should be treated as public.', 'dark-matter' ),
258 | 'readonly' => true,
259 | 'required' => false,
260 | 'type' => 'integer',
261 | ),
262 | 'archived' => array(
263 | 'context' => array( 'view', 'edit' ),
264 | 'description' => __( 'Whether the site should be treated as archived.', 'dark-matter' ),
265 | 'readonly' => true,
266 | 'required' => false,
267 | 'type' => 'boolean',
268 | ),
269 | 'mature' => array(
270 | 'context' => array( 'view', 'edit' ),
271 | 'description' => __( 'Whether the site should be treated as mature.', 'dark-matter' ),
272 | 'readonly' => true,
273 | 'required' => false,
274 | 'type' => 'boolean',
275 | ),
276 | 'spam' => array(
277 | 'context' => array( 'view', 'edit' ),
278 | 'description' => __( 'Whether the site should be treated as spam.', 'dark-matter' ),
279 | 'readonly' => true,
280 | 'required' => false,
281 | 'type' => 'boolean',
282 | ),
283 | 'deleted' => array(
284 | 'context' => array( 'view', 'edit' ),
285 | 'description' => __( 'Whether the site should be treated as deleted.', 'dark-matter' ),
286 | 'readonly' => true,
287 | 'required' => false,
288 | 'type' => 'boolean',
289 | ),
290 | ),
291 | ),
292 | ),
293 | );
294 |
295 | return $schema;
296 | }
297 |
298 | /**
299 | * Return a list of Domains.
300 | *
301 | * @since 2.0.0
302 | *
303 | * @param WP_REST_Request $request Current request.
304 | * @return WP_REST_Response|mixed WP_REST_Response on success. WP_Error on failure.
305 | */
306 | public function get_items( $request ) {
307 | $site_id = null;
308 |
309 | /**
310 | * Handle the processing of the Site ID parameter if it is provided. If
311 | * not, then set the $site_id to the Current Blog ID unless it is the
312 | * main site calling this endpoint. For the main site, we return all the
313 | * Domains for all Sites on the WordPress Network.
314 | */
315 | if ( isset( $request['site_id'] ) ) {
316 | $site_id = $request['site_id'];
317 | } elseif ( ! is_main_site() ) {
318 | $site_id = get_current_blog_id();
319 | }
320 |
321 | $db = DarkMatter_Domains::instance();
322 |
323 | $response = array();
324 |
325 | $result = $db->get_domains( $site_id );
326 |
327 | /**
328 | * Return errors as-is. This is maintain consistency and parity with the
329 | * WP CLI commands.
330 | */
331 | if ( is_wp_error( $result ) ) {
332 | return rest_ensure_response( $result );
333 | }
334 |
335 | /**
336 | * Process the domains and prepare each for the JSON response.
337 | */
338 | foreach ( $result as $dm_domain ) {
339 | $response[] = $this->prepare_item_for_response( $dm_domain, $request );
340 | }
341 |
342 | return rest_ensure_response( $response );
343 | }
344 |
345 | /**
346 | * Checks if a given request has access to get a domain or list of domains.
347 | *
348 | * @since 2.0.0
349 | *
350 | * @param WP_REST_Request $request Current request.
351 | * @return boolean True if the current user is a Super Admin. False otherwise.
352 | */
353 | public function get_items_permissions_check( $request ) {
354 | /** This action is documented in domain-mapping/classes/class-dm-ui.php */
355 | return current_user_can( apply_filters( 'dark_matter_domain_permission', 'upgrade_network', 'rest-get' ) );
356 | }
357 |
358 | /**
359 | * Prepare item for adding to the database.
360 | *
361 | * @since 2.0.0
362 | *
363 | * @param WP_REST_Request $request Current request.
364 | * @return array Data provided by the call to the endpoint.
365 | */
366 | protected function prepare_item_for_database( $request ) {
367 | $item = array(
368 | 'domain' => '',
369 | 'is_primary' => null,
370 | 'is_https' => null,
371 | 'is_active' => null,
372 | 'type' => null,
373 | );
374 |
375 | $method = $request->get_method();
376 |
377 | foreach ( $item as $key => $default ) {
378 | $value = $default;
379 |
380 | if ( isset( $request[ $key ] ) ) {
381 | $value = $request[ $key ];
382 | }
383 |
384 | if ( WP_REST_Server::CREATABLE === $method && null === $value && 'is_primary' === $key ) {
385 | $value = false;
386 | }
387 |
388 | if ( WP_REST_Server::CREATABLE === $method && null === $value && 'is_https' === $key ) {
389 | $value = false;
390 | }
391 |
392 | if ( WP_REST_Server::CREATABLE === $method && null === $value && 'is_active' === $key ) {
393 | $value = true;
394 | }
395 |
396 | $item[ $key ] = $value;
397 | }
398 |
399 | return $item;
400 | }
401 |
402 | /**
403 | * Prepares a single domain output for response.
404 | *
405 | * @since 2.0.0
406 | *
407 | * @param DM_Domain $item Domain object to be prepared for response.
408 | * @param WP_REST_Request $request Current request.
409 | * @return array Prepared item for REST response.
410 | */
411 | public function prepare_item_for_response( $item, $request ) {
412 | $fields = $this->get_fields_for_response( $request );
413 |
414 | $data = array();
415 |
416 | if ( in_array( 'id', $fields, true ) ) {
417 | $data['id'] = $item->id;
418 | }
419 |
420 | if ( in_array( 'domain', $fields, true ) ) {
421 | $data['domain'] = $item->domain;
422 | }
423 |
424 | if ( in_array( 'is_primary', $fields, true ) ) {
425 | $data['is_primary'] = $item->is_primary;
426 | }
427 |
428 | if ( in_array( 'is_active', $fields, true ) ) {
429 | $data['is_active'] = $item->active;
430 | }
431 |
432 | if ( in_array( 'is_https', $fields, true ) ) {
433 | $data['is_https'] = $item->is_https;
434 | }
435 |
436 | if ( in_array( 'type', $fields, true ) ) {
437 | $data['type'] = $item->type;
438 | }
439 |
440 | if ( in_array( 'site', $fields, true ) ) {
441 | $site_data = get_site( $item->blog_id );
442 |
443 | if ( ! empty( $site_data ) ) {
444 | $data['site'] = $site_data->to_array();
445 |
446 | $data['site']['blog_id'] = absint( $data['site']['blog_id'] );
447 | $data['site']['site_id'] = absint( $data['site']['site_id'] );
448 | $data['site']['public'] = absint( $data['site']['public'] );
449 | $data['site']['archived'] = boolval( $data['site']['archived'] );
450 | $data['site']['mature'] = boolval( $data['site']['mature'] );
451 | $data['site']['spam'] = boolval( $data['site']['spam'] );
452 | $data['site']['deleted'] = boolval( $data['site']['deleted'] );
453 |
454 | if ( '0000-00-00 00:00:00' === $data['site']['registered'] ) {
455 | $data['site']['registered'] = null;
456 | } else {
457 | $data['site']['registered'] = mysql_to_rfc3339( $data['site']['registered'] );
458 | }
459 |
460 | if ( '0000-00-00 00:00:00' === $data['site']['last_updated'] ) {
461 | $data['site']['last_updated'] = null;
462 | } else {
463 | $data['site']['last_updated'] = mysql_to_rfc3339( $data['site']['last_updated'] );
464 | }
465 | } else {
466 | $data['site'] = null;
467 | }
468 | }
469 |
470 | return $data;
471 | }
472 |
473 | /**
474 | * Register the routes for the REST API.
475 | *
476 | * @since 2.0.0
477 | *
478 | * @return void
479 | */
480 | public function register_routes() {
481 | register_rest_route(
482 | $this->namespace,
483 | $this->rest_base,
484 | array(
485 | 'methods' => WP_REST_Server::CREATABLE,
486 | 'callback' => array( $this, 'create_item' ),
487 | 'permission_callback' => array( $this, 'create_item_permissions_check' ),
488 | 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
489 | )
490 | );
491 |
492 | register_rest_route(
493 | $this->namespace,
494 | $this->rest_base . '/(?P.+)',
495 | array(
496 | 'args' => array(
497 | 'domain' => array(
498 | 'description' => __( 'Site ID to retrieve a list of Domains.', 'dark-matter' ),
499 | 'required' => true,
500 | 'type' => 'string',
501 | ),
502 | ),
503 | array(
504 | 'methods' => WP_REST_Server::READABLE,
505 | 'callback' => array( $this, 'get_item' ),
506 | 'permission_callback' => array( $this, 'get_items_permissions_check' ),
507 | 'schema' => array( $this, 'get_item_schema' ),
508 | ),
509 | array(
510 | 'methods' => WP_REST_Server::DELETABLE,
511 | 'callback' => array( $this, 'delete_item' ),
512 | 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
513 | 'args' => array(
514 | 'force' => array(
515 | 'default' => false,
516 | 'description' => __( 'Force Dark Matter to remove the domain. This is required if you wish to remove a Primary domain from a Site.', 'dark-matter' ),
517 | 'type' => 'boolean',
518 | ),
519 | ),
520 | ),
521 | array(
522 | 'methods' => WP_REST_Server::EDITABLE,
523 | 'callback' => array( $this, 'update_item' ),
524 | 'permission_callback' => array( $this, 'update_item_permissions_check' ),
525 | 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
526 | ),
527 | )
528 | );
529 |
530 | register_rest_route(
531 | $this->namespace,
532 | $this->rest_base_plural,
533 | array(
534 | 'methods' => WP_REST_Server::READABLE,
535 | 'callback' => array( $this, 'get_items' ),
536 | 'permission_callback' => array( $this, 'get_items_permissions_check' ),
537 | 'schema' => array( $this, 'get_item_schema' ),
538 | )
539 | );
540 |
541 | register_rest_route(
542 | $this->namespace,
543 | $this->rest_base_plural . '/(?P[\d]+)',
544 | array(
545 | 'args' => array(
546 | 'site_id' => array(
547 | 'description' => __( 'Site ID to retrieve a list of Domains.', 'dark-matter' ),
548 | 'required' => true,
549 | 'type' => 'integer',
550 | ),
551 | ),
552 | array(
553 | 'methods' => WP_REST_Server::READABLE,
554 | 'callback' => array( $this, 'get_items' ),
555 | 'permission_callback' => array( $this, 'get_items_permissions_check' ),
556 | 'schema' => array( $this, 'get_item_schema' ),
557 | ),
558 | )
559 | );
560 | }
561 |
562 | /**
563 | * Update a domain for a Site.
564 | *
565 | * @since 2.0.0
566 | *
567 | * @param WP_REST_Request $request Current request.
568 | * @return WP_REST_Response|mixed WP_REST_Response on success. WP_Error on failure.
569 | */
570 | public function update_item( $request ) {
571 | $db = DarkMatter_Domains::instance();
572 |
573 | $item = $this->prepare_item_for_database( $request );
574 |
575 | $result = $db->update(
576 | $item['domain'],
577 | $item['is_primary'],
578 | $item['is_https'],
579 | $request['force'],
580 | $item['is_active'],
581 | $item['type']
582 | );
583 |
584 | /**
585 | * Return errors as-is. This is maintain consistency and parity with the
586 | * WP CLI commands.
587 | */
588 | if ( is_wp_error( $result ) ) {
589 | return rest_ensure_response( $result );
590 | }
591 |
592 | /**
593 | * Prepare response for successfully adding a domain.
594 | */
595 | $response = $this->prepare_item_for_response( $result, $request );
596 | $response = rest_ensure_response( $response );
597 |
598 | return $response;
599 | }
600 |
601 | /**
602 | * Checks if a given request has access to update a domain.
603 | *
604 | * @since 2.0.0
605 | *
606 | * @param WP_REST_Request $request Current request.
607 | * @return boolean True if the current user is a Super Admin. False otherwise.
608 | */
609 | public function update_item_permissions_check( $request ) {
610 | /** This action is documented in domain-mapping/classes/class-dm-ui.php */
611 | return current_user_can( apply_filters( 'dark_matter_domain_permission', 'upgrade_network', 'rest-update' ) );
612 | }
613 | }
614 |
615 | /**
616 | * Setup the REST Controller for Domains for use.
617 | *
618 | * @since 2.0.0
619 | *
620 | * @return void
621 | */
622 | function dark_matter_domains_rest() {
623 | $controller = new DM_REST_Domains_Controller();
624 | $controller->register_routes();
625 | }
626 | add_action( 'rest_api_init', 'dark_matter_domains_rest' );
627 |
--------------------------------------------------------------------------------