/tr> 44
+          "configurations": {
45
+            "production": {
46
+              "fileReplacements": [
47
+                {
48
+                  "replace": "src/environments/environment.ts",
49
+                  "with": "src/environments/environment.prod.ts"
50
+                }
51
+              ],
52
+              "optimization": true,
53
+              "outputHashing": "all",
54
+              "sourceMap": false,
55
+              "namedChunks": false,
56
+              "aot": true,
57
+              "extractLicenses": true,
58
+              "vendorChunk": false,
59
+              "buildOptimizer": true,
60
+              "budgets": [
61
+                {
62
+                  "type": "initial",
63
+                  "maximumWarning": "2mb",
64
+                  "maximumError": "5mb"
65
+                }
66
+              ]
67
+            },
68
+            "ci": {
69
+              "progress": false
70
+            }
71
+          }
72
+        },
73
+        "serve": {
74
+          "builder": "@angular-devkit/build-angular:dev-server",
75
+          "options": {
76
+            "browserTarget": "app:build"
77
+          },
78
+          "configurations": {
79
+            "production": {
80
+              "browserTarget": "app:build:production"
81
+            },
82
+            "ci": {
83
+              "progress": false
84
+            }
85
+          }
86
+        },
87
+        "extract-i18n": {
88
+          "builder": "@angular-devkit/build-angular:extract-i18n",
89
+          "options": {
90
+            "browserTarget": "app:build"
91
+          }
92
+        },
93
+        "test": {
94
+          "builder": "@angular-devkit/build-angular:karma",
95
+          "options": {
96
+            "main": "src/test.ts",
97
+            "polyfills": "src/polyfills.ts",
98
+            "tsConfig": "tsconfig.spec.json",
99
+            "karmaConfig": "karma.conf.js",
100
+            "styles": [],
101
+            "scripts": [],
102
+            "assets": [
103
+              {
104
+                "glob": "favicon.ico",
105
+                "input": "src/",
106
+                "output": "/"
107
+              },
108
+              {
109
+                "glob": "**/*",
110
+                "input": "src/assets",
111
+                "output": "/assets"
112
+              }
113
+            ]
114
+          },
115
+          "configurations": {
116
+            "ci": {
117
+              "progress": false,
118
+              "watch": false
119
+            }
120
+          }
121
+        },
122
+        "lint": {
123
+          "builder": "@angular-eslint/builder:lint",
124
+          "options": {
125
+            "lintFilePatterns": [
126
+              "src/**/*.ts",
127
+              "src/**/*.html"
128
+            ]
129
+          }
130
+        },
131
+        "e2e": {
132
+          "builder": "@angular-devkit/build-angular:protractor",
133
+          "options": {
134
+            "protractorConfig": "e2e/protractor.conf.js",
135
+            "devServerTarget": "app:serve"
136
+          },
137
+          "configurations": {
138
+            "production": {
139
+              "devServerTarget": "app:serve:production"
140
+            },
141
+            "ci": {
142
+              "devServerTarget": "app:serve:ci"
143
+            }
144
+          }
145
+        },
146
+        "ionic-cordova-build": {
147
+          "builder": "@ionic/angular-toolkit:cordova-build",
148
+          "options": {
149
+            "browserTarget": "app:build"
150
+          },
151
+          "configurations": {
152
+            "production": {
153
+              "browserTarget": "app:build:production"
154
+            }
155
+          }
156
+        },
157
+        "ionic-cordova-serve": {
158
+          "builder": "@ionic/angular-toolkit:cordova-serve",
159
+          "options": {
160
+            "cordovaBuildTarget": "app:ionic-cordova-build",
161
+            "devServerTarget": "app:serve"
162
+          },
163
+          "configurations": {
164
+            "production": {
165
+              "cordovaBuildTarget": "app:ionic-cordova-build:production",
166
+              "devServerTarget": "app:serve:production"
167
+            }
168
+          }
169
+        }
170
+      }
171
+    }
172
+  },
173
+  "cli": {
174
+    "analytics": false,
175
+    "defaultCollection": "@ionic/angular-toolkit"
176
+  },
177
+  "schematics": {
178
+    "@ionic/angular-toolkit:component": {
179
+      "styleext": "scss"
180
+    },
181
+    "@ionic/angular-toolkit:page": {
182
+      "styleext": "scss"
183
+    }
184
+  }
185
+}

+ 107 - 0
config.xml

@@ -0,0 +1,107 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="net.simplico.farmster" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>FarmSter</name>
4
+    <description>We are the Best Friend of All Farmers</description>
5
+    <author email="patumos@gmail.com" href="https://www.simplico.net">Simplico Team</author>
6
+    <content src="index.html" />
7
+    <access origin="*" />
8
+    <allow-intent href="http://*/*" />
9
+    <allow-intent href="https://*/*" />
10
+    <allow-intent href="tel:*" />
11
+    <allow-intent href="sms:*" />
12
+    <allow-intent href="mailto:*" />
13
+    <allow-intent href="geo:*" />
14
+    <preference name="ScrollEnabled" value="false" />
15
+    <preference name="BackupWebStorage" value="none" />
16
+    <preference name="SplashMaintainAspectRatio" value="true" />
17
+    <preference name="FadeSplashScreenDuration" value="300" />
18
+    <preference name="SplashShowOnlyFirstTime" value="false" />
19
+    <preference name="SplashScreen" value="screen" />
20
+    <preference name="SplashScreenDelay" value="3000" />
21
+    <preference name="android-targetSdkVersion" value="30" />
22
+    <preference name="android-minSdkVersion" value="16" />
23
+    <platform name="android">
24
+        <edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application" xmlns:android="http://schemas.android.com/apk/res/android">
25
+            <application android:networkSecurityConfig="@xml/network_security_config" />
26
+        </edit-config>
27
+        <resource-file src="resources/android/xml/network_security_config.xml" target="app/src/main/res/xml/network_security_config.xml" />
28
+        <allow-intent href="market:*" />
29
+        <icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
30
+        <icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
31
+        <icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
32
+        <icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
33
+        <icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
34
+        <icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
35
+        <splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
36
+        <splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
37
+        <splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
38
+        <splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
39
+        <splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
40
+        <splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
41
+        <splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
42
+        <splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
43
+        <splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
44
+        <splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
45
+        <splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
46
+        <splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
47
+    </platform>
48
+    <platform name="ios">
49
+        <allow-intent href="itms:*" />
50
+        <allow-intent href="itms-apps:*" />
51
+        <allow-intent href="tel:*" />
52
+        <edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription">
53
+            <string>Use current location to find nearby businesses</string>
54
+        </edit-config>
55
+        <icon height="57" src="resources/ios/icon/icon.png" width="57" />
56
+        <icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
57
+        <icon height="20" src="resources/ios/icon/icon-20.png" width="20" />
58
+        <icon height="40" src="resources/ios/icon/icon-20@2x.png" width="40" />
59
+        <icon height="60" src="resources/ios/icon/icon-20@3x.png" width="60" />
60
+        <icon height="48" src="resources/ios/icon/icon-24@2x.png" width="48" />
61
+        <icon height="55" src="resources/ios/icon/icon-27.5@2x.png" width="55" />
62
+        <icon height="29" src="resources/ios/icon/icon-29.png" width="29" />
63
+        <icon height="58" src="resources/ios/icon/icon-29@2x.png" width="58" />
64
+        <icon height="87" src="resources/ios/icon/icon-29@3x.png" width="87" />
65
+        <icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
66
+        <icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80" />
67
+        <icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120" />
68
+        <icon height="88" src="resources/ios/icon/icon-44@2x.png" width="88" />
69
+        <icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
70
+        <icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100" />
71
+        <icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
72
+        <icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120" />
73
+        <icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180" />
74
+        <icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
75
+        <icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144" />
76
+        <icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
77
+        <icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152" />
78
+        <icon height="167" src="resources/ios/icon/icon-83.5@2x.png" width="167" />
79
+        <icon height="172" src="resources/ios/icon/icon-86@2x.png" width="172" />
80
+        <icon height="196" src="resources/ios/icon/icon-98@2x.png" width="196" />
81
+        <icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
82
+        <splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
83
+        <splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
84
+        <splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
85
+        <splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
86
+        <splash height="1125" src="resources/ios/splash/Default-Landscape-2436h.png" width="2436" />
87
+        <splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208" />
88
+        <splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536" />
89
+        <splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048" />
90
+        <splash height="2732" src="resources/ios/splash/Default-Portrait@~ipadpro.png" width="2048" />
91
+        <splash height="2048" src="resources/ios/splash/Default-Landscape@~ipadpro.png" width="2732" />
92
+        <splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640" />
93
+        <splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
94
+        <splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242" />
95
+        <splash height="2436" src="resources/ios/splash/Default-2436h.png" width="1125" />
96
+        <splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
97
+        <icon height="216" src="resources/ios/icon/icon-108@2x.png" width="216" />
98
+        <splash height="2688" src="resources/ios/splash/Default-2688h~iphone.png" width="1242" />
99
+        <splash height="1242" src="resources/ios/splash/Default-Landscape-2688h~iphone.png" width="2688" />
100
+        <splash height="1792" src="resources/ios/splash/Default-1792h~iphone.png" width="828" />
101
+        <splash height="828" src="resources/ios/splash/Default-Landscape-1792h~iphone.png" width="1792" />
102
+    </platform>
103
+    <plugin name="cordova-plugin-ionic-keyboard" spec="^2.0.5" />
104
+    <allow-navigation href="http://172.16.112.36:8102" sessionid="56964c11" />
105
+    <allow-navigation href="http://localhost:8101" sessionid="9c146598" />
106
+    <allow-navigation href="http://localhost:8100" sessionid="39e2137c" />
107
+</widget>

+ 37 - 0
e2e/protractor.conf.js

@@ -0,0 +1,37 @@
1
+// @ts-check
2
+// Protractor configuration file, see link for more information
3
+// https://github.com/angular/protractor/blob/master/lib/config.ts
4
+
5
+const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
6
+
7
+/**
8
+ * @type { import("protractor").Config }
9
+ */
10
+exports.config = {
11
+  allScriptsTimeout: 11000,
12
+  specs: [
13
+    './src/**/*.e2e-spec.ts'
14
+  ],
15
+  capabilities: {
16
+    browserName: 'chrome'
17
+  },
18
+  directConnect: true,
19
+  SELENIUM_PROMISE_MANAGER: false,
20
+  baseUrl: 'http://localhost:4200/',
21
+  framework: 'jasmine',
22
+  jasmineNodeOpts: {
23
+    showColors: true,
24
+    defaultTimeoutInterval: 30000,
25
+    print: function() {}
26
+  },
27
+  onPrepare() {
28
+    require('ts-node').register({
29
+      project: require('path').join(__dirname, './tsconfig.json')
30
+    });
31
+    jasmine.getEnv().addReporter(new SpecReporter({
32
+      spec: {
33
+        displayStacktrace: StacktraceOption.PRETTY
34
+      }
35
+    }));
36
+  }
37
+};

+ 14 - 0
e2e/src/app.e2e-spec.ts

@@ -0,0 +1,14 @@
1
+import { AppPage } from './app.po';
2
+
3
+describe('new App', () => {
4
+  let page: AppPage;
5
+
6
+  beforeEach(() => {
7
+    page = new AppPage();
8
+  });
9
+
10
+  it('should display welcome message', () => {
11
+    page.navigateTo();
12
+    expect(page.getPageTitle()).toContain('Tab 1');
13
+  });
14
+});

+ 11 - 0
e2e/src/app.po.ts

@@ -0,0 +1,11 @@
1
+import { browser, by, element } from 'protractor';
2
+
3
+export class AppPage {
4
+  navigateTo() {
5
+    return browser.get('/');
6
+  }
7
+
8
+  getPageTitle() {
9
+    return element(by.css('ion-title')).getText();
10
+  }
11
+}

+ 12 - 0
e2e/tsconfig.json

@@ -0,0 +1,12 @@
1
+{
2
+  "extends": "../tsconfig.json",
3
+  "compilerOptions": {
4
+    "outDir": "../out-tsc/e2e",
5
+    "module": "commonjs",
6
+    "target": "es2018",
7
+    "types": [
8
+      "jasmine",
9
+      "node"
10
+    ]
11
+  }
12
+}

+ 39 - 0
google-services.json

@@ -0,0 +1,39 @@
1
+{
2
+  "project_info": {
3
+    "project_number": "1031089119738",
4
+    "project_id": "farmster-c87c8",
5
+    "storage_bucket": "farmster-c87c8.appspot.com"
6
+  },
7
+  "client": [
8
+    {
9
+      "client_info": {
10
+        "mobilesdk_app_id": "1:1031089119738:android:cf99beb19818a7e4d0c443",
11
+        "android_client_info": {
12
+          "package_name": "net.simplico.farmster"
13
+        }
14
+      },
15
+      "oauth_client": [
16
+        {
17
+          "client_id": "1031089119738-pc9qukqqd30aebdh33q51asvbvn6ndhf.apps.googleusercontent.com",
18
+          "client_type": 3
19
+        }
20
+      ],
21
+      "api_key": [
22
+        {
23
+          "current_key": "AIzaSyCviHmTUdl_TL_pR8catbY-gyibTnooUv0"
24
+        }
25
+      ],
26
+      "services": {
27
+        "appinvite_service": {
28
+          "other_platform_oauth_client": [
29
+            {
30
+              "client_id": "1031089119738-pc9qukqqd30aebdh33q51asvbvn6ndhf.apps.googleusercontent.com",
31
+              "client_type": 3
32
+            }
33
+          ]
34
+        }
35
+      }
36
+    }
37
+  ],
38
+  "configuration_version": "1"
39
+}

+ 7 - 0
ionic.config.json

@@ -0,0 +1,7 @@
1
+{
2
+  "name": "hp",
3
+  "integrations": {
4
+    "cordova": {}
5
+  },
6
+  "type": "angular"
7
+}

+ 44 - 0
karma.conf.js

@@ -0,0 +1,44 @@
1
+// Karma configuration file, see link for more information
2
+// https://karma-runner.github.io/1.0/config/configuration-file.html
3
+
4
+module.exports = function (config) {
5
+  config.set({
6
+    basePath: '',
7
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
8
+    plugins: [
9
+      require('karma-jasmine'),
10
+      require('karma-chrome-launcher'),
11
+      require('karma-jasmine-html-reporter'),
12
+      require('karma-coverage'),
13
+      require('@angular-devkit/build-angular/plugins/karma')
14
+    ],
15
+    client: {
16
+      jasmine: {
17
+        // you can add configuration options for Jasmine here
18
+        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19
+        // for example, you can disable the random execution with `random: false`
20
+        // or set a specific seed with `seed: 4321`
21
+      },
22
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
23
+    },
24
+    jasmineHtmlReporter: {
25
+      suppressAll: true // removes the duplicated traces
26
+    },
27
+    coverageReporter: {
28
+      dir: require('path').join(__dirname, './coverage/ngv'),
29
+      subdir: '.',
30
+      reporters: [
31
+        { type: 'html' },
32
+        { type: 'text-summary' }
33
+      ]
34
+    },
35
+    reporters: ['progress', 'kjhtml'],
36
+    port: 9876,
37
+    colors: true,
38
+    logLevel: config.LOG_INFO,
39
+    autoWatch: true,
40
+    browsers: ['Chrome'],
41
+    singleRun: false,
42
+    restartOnFileChange: true
43
+  });
44
+};

BIN
my-release-key.keystore


Datei-Diff unterdrückt, da er zu groß ist
+ 18344 - 0
package-lock.json


+ 180 - 0
package.json

@@ -0,0 +1,180 @@
1
+{
2
+  "name": "hp",
3
+  "version": "0.0.1",
4
+  "author": "Ionic Framework",
5
+  "homepage": "https://ionicframework.com/",
6
+  "scripts": {
7
+    "ng": "ng",
8
+    "start": "ng serve",
9
+    "build": "ng build",
10
+    "test": "ng test",
11
+    "lint": "ng lint",
12
+    "e2e": "ng e2e"
13
+  },
14
+  "private": true,
15
+  "dependencies": {
16
+    "@angular/common": "~13.0.3",
17
+    "@angular/compiler": "~13.0.3",
18
+    "@angular/core": "~13.0.3",
19
+    "@angular/forms": "~13.0.3",
20
+    "@angular/platform-browser": "~13.0.3",
21
+    "@angular/platform-browser-dynamic": "~13.0.3",
22
+    "@angular/router": "~13.0.3",
23
+    "@awesome-cordova-plugins/barcode-scanner": "^5.37.1",
24
+    "@awesome-cordova-plugins/core": "^5.37.1",
25
+    "@awesome-cordova-plugins/file-transfer": "^5.37.1",
26
+    "@awesome-cordova-plugins/push": "^5.37.1",
27
+    "@awesome-cordova-plugins/status-bar": "^5.37.2",
28
+    "@awesome-cordova-plugins/stripe": "^5.37.1",
29
+    "@capacitor/app": "1.0.6",
30
+    "@capacitor/core": "3.3.2",
31
+    "@capacitor/haptics": "1.1.3",
32
+    "@capacitor/ios": "^3.3.2",
33
+    "@capacitor/keyboard": "1.1.3",
34
+    "@capacitor/status-bar": "1.0.6",
35
+    "@fortawesome/angular-fontawesome": "^0.10.1",
36
+    "@fortawesome/fontawesome-free": "^5.15.4",
37
+    "@fortawesome/fontawesome-svg-core": "^1.2.36",
38
+    "@fortawesome/free-brands-svg-icons": "^5.15.4",
39
+    "@fortawesome/free-regular-svg-icons": "^5.15.4",
40
+    "@fortawesome/free-solid-svg-icons": "^5.15.4",
41
+    "@ionic-native/android-permissions": "^5.30.0",
42
+    "@ionic-native/call-number": "^5.36.0",
43
+    "@ionic-native/core": "^5.36.0",
44
+    "@ionic-native/device": "^5.30.0",
45
+    "@ionic-native/email-composer": "^5.36.0",
46
+    "@ionic-native/facebook": "^5.36.0",
47
+    "@ionic-native/geolocation": "^5.36.0",
48
+    "@ionic-native/http": "^5.36.0",
49
+    "@ionic-native/in-app-browser": "^5.36.0",
50
+    "@ionic-native/native-geocoder": "^5.36.0",
51
+    "@ionic-native/paypal": "^5.30.0",
52
+    "@ionic-native/push": "^5.36.0",
53
+    "@ionic-native/sign-in-with-apple": "^5.36.0",
54
+    "@ionic-native/uid": "^5.30.0",
55
+    "@ionic/angular": "^5.9.1",
56
+    "@ionic/storage": "^3.0.6",
57
+    "@ionic/storage-angular": "^3.0.6",
58
+    "abbrev": "^1.1.1",
59
+    "cordova-plugin-device": "2.0.3",
60
+    "cordova-plugin-ionic-webview": "5.0.0",
61
+    "cordova-plugin-splashscreen": "6.0.0",
62
+    "cordova-plugin-whitelist": "1.3.5",
63
+    "ionic-img-viewer": "^2.9.0",
64
+    "lru-cache": "^6.0.0",
65
+    "mimic-fn": "^4.0.0",
66
+    "onetime": "^6.0.0",
67
+    "osenv": "^0.1.5",
68
+    "resolve-url": "^0.2.1",
69
+    "rxjs": "~7.4.0",
70
+    "sharp": "^0.29.3",
71
+    "string.prototype.codepointat": "^1.0.0",
72
+    "tslib": "^2.3.1",
73
+    "yallist": "^4.0.0",
74
+    "zone.js": "~0.11.4"
75
+  },
76
+  "devDependencies": {
77
+    "@angular-devkit/build-angular": "~13.0.4",
78
+    "@angular-eslint/builder": "~13.0.1",
79
+    "@angular-eslint/eslint-plugin": "~13.0.1",
80
+    "@angular-eslint/eslint-plugin-template": "~13.0.1",
81
+    "@angular-eslint/template-parser": "~13.0.1",
82
+    "@angular/cli": "^13.0.4",
83
+    "@angular/compiler": "~13.0.3",
84
+    "@angular/compiler-cli": "~13.0.3",
85
+    "@angular/language-service": "~13.0.3",
86
+    "@capacitor/cli": "3.3.2",
87
+    "@ionic/angular-toolkit": "^5.0.3",
88
+    "@types/jasmine": "~3.10.2",
89
+    "@types/jasminewd2": "~2.0.10",
90
+    "@types/node": "^16.11.11",
91
+    "@typescript-eslint/eslint-plugin": "4.16.1",
92
+    "@typescript-eslint/parser": "4.16.1",
93
+    "call-number": "^1.0.1",
94
+    "card.io.cordova.mobilesdk": "^2.1.0",
95
+    "com.paypal.cordova.mobilesdk": "^3.5.0",
96
+    "cordova-android": "^9.1.0",
97
+    "cordova-ios": "^6.2.0",
98
+    "cordova-plugin-add-swift-support": "^2.0.2",
99
+    "cordova-plugin-advanced-http": "^3.2.2",
100
+    "cordova-plugin-android-permissions": "^1.1.2",
101
+    "cordova-plugin-androidx": "^3.0.0",
102
+    "cordova-plugin-androidx-adapter": "^1.1.3",
103
+    "cordova-plugin-email-composer": "^0.10.0",
104
+    "cordova-plugin-facebook-connect": "^3.2.0",
105
+    "cordova-plugin-file": "^6.0.2",
106
+    "cordova-plugin-file-transfer": "^1.7.1",
107
+    "cordova-plugin-geolocation": "^4.1.0",
108
+    "cordova-plugin-googlemaps": "^2.7.1",
109
+    "cordova-plugin-googlemaps-sdk": "github:mapsplugin/cordova-plugin-googlemaps-sdk",
110
+    "cordova-plugin-inappbrowser": "^5.0.0",
111
+    "cordova-plugin-ionic-keyboard": "^2.2.0",
112
+    "cordova-plugin-nativegeocoder": "^3.4.1",
113
+    "cordova-plugin-sign-in-with-apple": "^0.1.2",
114
+    "cordova-plugin-stripe": "^1.5.3",
115
+    "cordova-plugin-uid": "^1.3.0",
116
+    "cordova-support-google-services": "^1.4.1",
117
+    "eslint": "^8.3.0",
118
+    "eslint-plugin-import": "2.25.3",
119
+    "eslint-plugin-jsdoc": "37.1.0",
120
+    "eslint-plugin-prefer-arrow": "1.2.3",
121
+    "jasmine-core": "~3.10.1",
122
+    "jasmine-spec-reporter": "~7.0.0",
123
+    "karma": "~6.3.9",
124
+    "karma-chrome-launcher": "~3.1.0",
125
+    "karma-coverage": "~2.1.0",
126
+    "karma-coverage-istanbul-reporter": "~3.0.3",
127
+    "karma-jasmine": "~4.0.1",
128
+    "karma-jasmine-html-reporter": "^1.7.0",
129
+    "mx.ferreyra.callnumber": "0.0.2",
130
+    "phonegap-plugin-multidex": "^1.0.0",
131
+    "phonegap-plugin-push": "^2.3.0",
132
+    "protractor": "~7.0.0",
133
+    "ts-node": "~10.4.0",
134
+    "typescript": "~4.2.4"
135
+  },
136
+  "description": "An Ionic project",
137
+  "cordova": {
138
+    "plugins": {
139
+      "cordova-plugin-ionic-keyboard": {},
140
+      "cordova-plugin-geolocation": {},
141
+      "cordova-plugin-nativegeocoder": {
142
+        "LOCATION_WHEN_IN_USE_DESCRIPTION": "Use geocoder service"
143
+      },
144
+      "mx.ferreyra.callnumber": {},
145
+      "cordova-plugin-email-composer": {},
146
+      "call-number": {},
147
+      "cordova-plugin-inappbrowser": {},
148
+      "cordova-plugin-sign-in-with-apple": {},
149
+      "cordova-plugin-androidx": {},
150
+      "cordova-plugin-androidx-adapter": {},
151
+      "phonegap-plugin-push": {},
152
+      "cordova-plugin-file-transfer": {},
153
+      "cordova-plugin-stripe": {},
154
+      "com.paypal.cordova.mobilesdk": {},
155
+      "cordova-plugin-whitelist": {},
156
+      "cordova-plugin-device": {},
157
+      "cordova-plugin-splashscreen": {},
158
+      "cordova-plugin-ionic-webview": {},
159
+      "cordova-plugin-facebook-connect": {
160
+        "APP_ID": "4746807965342529",
161
+        "APP_NAME": "FarmSter",
162
+        "FACEBOOK_URL_SCHEME_SUFFIX": " ",
163
+        "OTHER_APP_SCHEMES": " ",
164
+        "FACEBOOK_AUTO_LOG_APP_EVENTS": "true",
165
+        "FACEBOOK_HYBRID_APP_EVENTS": "false",
166
+        "FACEBOOK_ADVERTISER_ID_COLLECTION": "true",
167
+        "FACEBOOK_ANDROID_SDK_VERSION": "11.3.0",
168
+        "FACEBOOK_IOS_SDK_VERSION": "11.1.0",
169
+        "FACEBOOK_BROWSER_SDK_VERSION": "v11.0"
170
+      },
171
+      "cordova-plugin-statusbar": {},
172
+      "cordova-plugin-android-permissions": {},
173
+      "cordova-plugin-dreamover-uid": {}
174
+    },
175
+    "platforms": [
176
+      "ios",
177
+      "android"
178
+    ]
179
+  }
180
+}

+ 8 - 0
resources/README.md

@@ -0,0 +1,8 @@
1
+These are Cordova resources. You can replace icon.png and splash.png and run
2
+`ionic cordova resources` to generate custom icons and splash screens for your
3
+app. See `ionic cordova resources --help` for details.
4
+
5
+Cordova reference documentation:
6
+
7
+- Icons: https://cordova.apache.org/docs/en/latest/config_ref/images.html
8
+- Splash Screens: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/

BIN
resources/android/icon/drawable-hdpi-icon.png


BIN
resources/android/icon/drawable-ldpi-icon.png


BIN
resources/android/icon/drawable-mdpi-icon.png


BIN
resources/android/icon/drawable-xhdpi-icon.png


BIN
resources/android/icon/drawable-xxhdpi-icon.png


BIN
resources/android/icon/drawable-xxxhdpi-icon.png


BIN
resources/android/splash/drawable-land-hdpi-screen.png


BIN
resources/android/splash/drawable-land-ldpi-screen.png


BIN
resources/android/splash/drawable-land-mdpi-screen.png


BIN
resources/android/splash/drawable-land-xhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxxhdpi-screen.png


BIN
resources/android/splash/drawable-port-hdpi-screen.png


BIN
resources/android/splash/drawable-port-ldpi-screen.png


BIN
resources/android/splash/drawable-port-mdpi-screen.png


BIN
resources/android/splash/drawable-port-xhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxxhdpi-screen.png


+ 7 - 0
resources/android/xml/network_security_config.xml

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<network-security-config>
3
+    <domain-config cleartextTrafficPermitted="true">
4
+        <domain includeSubdomains="true">localhost</domain>
5
+        <domain includeSubdomains="true">tamtime.iamarray.xyz</domain>
6
+    </domain-config>
7
+</network-security-config>

BIN
resources/icon.png


BIN
resources/ios/icon/icon-1024.png


BIN
resources/ios/icon/icon-108@2x.png


BIN
resources/ios/icon/icon-20.png


BIN
resources/ios/icon/icon-20@2x.png


BIN
resources/ios/icon/icon-20@3x.png


BIN
resources/ios/icon/icon-24@2x.png


BIN
resources/ios/icon/icon-27.5@2x.png


BIN
resources/ios/icon/icon-29.png


BIN
resources/ios/icon/icon-29@2x.png


BIN
resources/ios/icon/icon-29@3x.png


BIN
resources/ios/icon/icon-40.png


BIN
resources/ios/icon/icon-40@2x.png


BIN
resources/ios/icon/icon-40@3x.png


BIN
resources/ios/icon/icon-44@2x.png


BIN
resources/ios/icon/icon-50.png


BIN
resources/ios/icon/icon-50@2x.png


BIN
resources/ios/icon/icon-60.png


BIN
resources/ios/icon/icon-60@2x.png


BIN
resources/ios/icon/icon-60@3x.png


BIN
resources/ios/icon/icon-72.png


BIN
resources/ios/icon/icon-72@2x.png


BIN
resources/ios/icon/icon-76.png


BIN
resources/ios/icon/icon-76@2x.png


BIN
resources/ios/icon/icon-83.5@2x.png


BIN
resources/ios/icon/icon-86@2x.png


BIN
resources/ios/icon/icon-98@2x.png


BIN
resources/ios/icon/icon.png


BIN
resources/ios/icon/icon@2x.png


BIN
resources/ios/splash/Default-1792h~iphone.png


BIN
resources/ios/splash/Default-2436h.png


BIN
resources/ios/splash/Default-2688h~iphone.png


BIN
resources/ios/splash/Default-568h@2x~iphone.png


BIN
resources/ios/splash/Default-667h.png


BIN
resources/ios/splash/Default-736h.png


BIN
resources/ios/splash/Default-Landscape-1792h~iphone.png


BIN
resources/ios/splash/Default-Landscape-2436h.png


BIN
resources/ios/splash/Default-Landscape-2688h~iphone.png


BIN
resources/ios/splash/Default-Landscape-736h.png


BIN
resources/ios/splash/Default-Landscape@2x~ipad.png


BIN
resources/ios/splash/Default-Landscape@~ipadpro.png


BIN
resources/ios/splash/Default-Landscape~ipad.png


BIN
resources/ios/splash/Default-Portrait@2x~ipad.png


BIN
resources/ios/splash/Default-Portrait@~ipadpro.png


BIN
resources/ios/splash/Default-Portrait~ipad.png


BIN
resources/ios/splash/Default@2x~iphone.png


BIN
resources/ios/splash/Default@2x~universal~anyany.png


BIN
resources/ios/splash/Default~iphone.png


BIN
resources/splash.png


+ 92 - 0
src/app/app-routing.module.ts

@@ -0,0 +1,92 @@
1
+import { NgModule } from '@angular/core';
2
+import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
3
+
4
+const routes: Routes = [
5
+  {
6
+    path: '',
7
+    loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
8
+  },
9
+  {
10
+    path: 'home',
11
+    loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
12
+  },
13
+  {
14
+    path: 'register',
15
+    loadChildren: () => import('./register/register.module').then( m => m.RegisterPageModule)
16
+  },
17
+  {
18
+    path: 'login',
19
+    loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule)
20
+  },
21
+  {
22
+    path: 'forgot-password',
23
+    loadChildren: () => import('./forgot-password/forgot-password.module').then( m => m.ForgotPasswordPageModule)
24
+  },
25
+  {
26
+    path: 'register-form',
27
+    loadChildren: () => import('./register-form/register-form.module').then( m => m.RegisterFormPageModule)
28
+  },
29
+  {
30
+    path: 'profile',
31
+    loadChildren: () => import('./profile/profile.module').then( m => m.ProfilePageModule)
32
+  },
33
+  {
34
+    path: 'reset-password',
35
+    loadChildren: () => import('./reset-password/reset-password.module').then( m => m.ResetPasswordPageModule)
36
+  },
37
+  {
38
+    path: 'placedetail',
39
+    loadChildren: () => import('./placedetail/placedetail.module').then( m => m.PlacedetailPageModule)
40
+  },
41
+  {
42
+    path: 'place',
43
+    loadChildren: () => import('./place/place.module').then( m => m.PlacePageModule)
44
+  },
45
+  {
46
+    path: 'province',
47
+    loadChildren: () => import('./province/province.module').then( m => m.ProvincePageModule)
48
+  },
49
+  {
50
+    path: 'gmap',
51
+    loadChildren: () => import('./gmap/gmap.module').then( m => m.GmapPageModule)
52
+  },
53
+  {
54
+    path: 'nearme',
55
+    loadChildren: () => import('./nearme/nearme.module').then( m => m.NearmePageModule)
56
+  },
57
+  {
58
+    path: 'sample',
59
+    loadChildren: () => import('./sample/sample.module').then( m => m.SamplePageModule)
60
+  },
61
+  {
62
+    path: 'product',
63
+    loadChildren: () => import('./product/product.module').then( m => m.ProductPageModule)
64
+  },
65
+  {
66
+    path: 'product-detail',
67
+    loadChildren: () => import('./product-detail/product-detail.module').then( m => m.ProductDetailPageModule)
68
+  },
69
+  {
70
+    path: 'tracking',
71
+    loadChildren: () => import('./tracking/tracking.module').then( m => m.TrackingPageModule)
72
+  },
73
+  {
74
+    path: 'news',
75
+    loadChildren: () => import('./news/news.module').then( m => m.NewsPageModule)
76
+  },
77
+  {
78
+    path: 'checkout',
79
+    loadChildren: () => import('./checkout/checkout.module').then( m => m.CheckoutPageModule)
80
+  },
81
+  {
82
+    path: 'pay',
83
+    loadChildren: () => import('./pay/pay.module').then( m => m.PayPageModule)
84
+  }
85
+];
86
+@NgModule({
87
+  imports: [
88
+    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
89
+  ],
90
+  exports: [RouterModule]
91
+})
92
+export class AppRoutingModule {}

+ 3 - 0
src/app/app.component.html

@@ -0,0 +1,3 @@
1
+<ion-app>
2
+  <ion-router-outlet></ion-router-outlet>
3
+</ion-app>

+ 4 - 0
src/app/app.component.scss

@@ -0,0 +1,4 @@
1
+.product-title {
2
+    font-weight:bold;
3
+    font-size:20px;
4
+}

+ 23 - 0
src/app/app.component.spec.ts

@@ -0,0 +1,23 @@
1
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2
+import { TestBed, waitForAsync } from '@angular/core/testing';
3
+
4
+import { AppComponent } from './app.component';
5
+
6
+describe('AppComponent', () => {
7
+
8
+  beforeEach(waitForAsync(() => {
9
+
10
+    TestBed.configureTestingModule({
11
+      declarations: [AppComponent],
12
+      schemas: [CUSTOM_ELEMENTS_SCHEMA],
13
+    }).compileComponents();
14
+  }));
15
+
16
+  it('should create the app', () => {
17
+    const fixture = TestBed.createComponent(AppComponent);
18
+    const app = fixture.debugElement.componentInstance;
19
+    expect(app).toBeTruthy();
20
+  });
21
+  // TODO: add more tests!
22
+
23
+});

+ 52 - 0
src/app/app.component.ts

@@ -0,0 +1,52 @@
1
+import { Component } from '@angular/core';
2
+import { Storage } from '@ionic/storage-angular';
3
+import { StatusBar } from '@awesome-cordova-plugins/status-bar/ngx';
4
+import { Uid } from '@ionic-native/uid/ngx';
5
+import { AndroidPermissions } from '@ionic-native/android-permissions/ngx';
6
+
7
+
8
+@Component({
9
+  selector: 'app-root',
10
+  templateUrl: 'app.component.html',
11
+  styleUrls: ['app.component.scss'],
12
+})
13
+export class AppComponent {
14
+    constructor(private storage: Storage, private statusBar:StatusBar, private uid: Uid, private androidPermissions: AndroidPermissions) {
15
+
16
+        this.statusBar.overlaysWebView(true);
17
+
18
+
19
+    }
20
+    openQRCode() {
21
+        console.log("open QR Code");
22
+    }
23
+    async ngOnInit() {
24
+        // If using a custom driver:
25
+        // await this.storage.defineDriver(MyCustomDriver)
26
+        await this.storage.create();
27
+        //await this.getImei();
28
+    }
29
+    async getImei() {
30
+        const { hasPermission } = await this.androidPermissions.checkPermission(
31
+            this.androidPermissions.PERMISSION.READ_PHONE_STATE
32
+        );
33
+        console.log(hasPermission);
34
+
35
+        if (!hasPermission) {
36
+            const result = await this.androidPermissions.requestPermission(
37
+                this.androidPermissions.PERMISSION.READ_PHONE_STATE
38
+            );
39
+
40
+            if (!result.hasPermission) {
41
+                throw new Error('Permissions required');
42
+            }
43
+
44
+            // ok, a user gave us permission, we can get him identifiers after restart app
45
+            return;
46
+        }
47
+        console.log("UUID");
48
+        console.log(this.uid.UUID);
49
+        await this.storage.set('uid', this.uid.UUID);
50
+        return this.uid.IMEI
51
+    }
52
+}

+ 77 - 0
src/app/app.module.ts

@@ -0,0 +1,77 @@
1
+import { NgModule } from '@angular/core';
2
+import { BrowserModule } from '@angular/platform-browser';
3
+import { RouteReuseStrategy } from '@angular/router';
4
+
5
+import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
6
+
7
+import { AppRoutingModule } from './app-routing.module';
8
+import { AppComponent } from './app.component';
9
+
10
+import { HttpClientModule, HttpClient} from '@angular/common/http';
11
+
12
+// import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
13
+// import { library } from '@fortawesome/fontawesome-svg-core';
14
+import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
15
+import { fas } from '@fortawesome/free-solid-svg-icons';
16
+import { far } from '@fortawesome/free-regular-svg-icons';
17
+import { fab } from '@fortawesome/free-brands-svg-icons';
18
+
19
+import { Geolocation } from '@ionic-native/geolocation/ngx';
20
+import { NativeGeocoder } from '@ionic-native/native-geocoder/ngx';
21
+
22
+import { CallNumber } from '@ionic-native/call-number/ngx';
23
+import { EmailComposer } from '@ionic-native/email-composer/ngx';
24
+import { HTTP } from '@ionic-native/http/ngx';
25
+import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
26
+
27
+import { IonicStorageModule } from '@ionic/storage-angular';
28
+import { SignInWithApple } from '@ionic-native/sign-in-with-apple/ngx';
29
+
30
+import { PipeModule } from './pipe/pipe.module';
31
+import { Facebook } from '@ionic-native/facebook/ngx';
32
+
33
+import { BarcodeScanner } from '@awesome-cordova-plugins/barcode-scanner/ngx';
34
+import { PayPal } from '@ionic-native/paypal/ngx';
35
+import { StatusBar } from '@awesome-cordova-plugins/status-bar/ngx';
36
+import { Uid } from '@ionic-native/uid/ngx';
37
+import { AndroidPermissions } from '@ionic-native/android-permissions/ngx';
38
+import { Device } from '@ionic-native/device/ngx';
39
+
40
+//import { Stripe } from '@awesome-cordova-plugins/stripe/ngx';
41
+
42
+
43
+// import { IonicImageViewerModule } from 'ionic-img-viewer';
44
+
45
+
46
+// library.add(fas,far,fab);
47
+
48
+@NgModule({
49
+    declarations: [AppComponent],
50
+    entryComponents: [],
51
+    imports: [HttpClientModule, BrowserModule, PipeModule,IonicModule.forRoot({_forceStatusbarPadding: true}), AppRoutingModule, FontAwesomeModule,   IonicStorageModule.forRoot()],
52
+    providers: [
53
+        Geolocation,
54
+        NativeGeocoder,
55
+        CallNumber,
56
+        EmailComposer,
57
+        InAppBrowser,
58
+        HTTP,
59
+        Facebook, 
60
+        StatusBar,
61
+        BarcodeScanner,
62
+        SignInWithApple,
63
+        PayPal,
64
+        AndroidPermissions,
65
+        Uid,
66
+        Device,
67
+        //Stripe,
68
+        { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
69
+    ],
70
+    bootstrap: [AppComponent],
71
+})
72
+export class AppModule {
73
+    constructor(library: FaIconLibrary) {
74
+        library.addIconPacks(fas, fab, far);
75
+    }
76
+
77
+}

+ 17 - 0
src/app/checkout/checkout-routing.module.ts

@@ -0,0 +1,17 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule } from '@angular/router';
3
+
4
+import { CheckoutPage } from './checkout.page';
5
+
6
+const routes: Routes = [
7
+  {
8
+    path: '',
9
+    component: CheckoutPage
10
+  }
11
+];
12
+
13
+@NgModule({
14
+  imports: [RouterModule.forChild(routes)],
15
+  exports: [RouterModule],
16
+})
17
+export class CheckoutPageRoutingModule {}

+ 20 - 0
src/app/checkout/checkout.module.ts

@@ -0,0 +1,20 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { FormsModule } from '@angular/forms';
4
+
5
+import { IonicModule } from '@ionic/angular';
6
+
7
+import { CheckoutPageRoutingModule } from './checkout-routing.module';
8
+
9
+import { CheckoutPage } from './checkout.page';
10
+
11
+@NgModule({
12
+  imports: [
13
+    CommonModule,
14
+    FormsModule,
15
+    IonicModule,
16
+    CheckoutPageRoutingModule
17
+  ],
18
+  declarations: [CheckoutPage]
19
+})
20
+export class CheckoutPageModule {}

+ 0 - 0
src/app/checkout/checkout.page.html


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.

tum/whitesports - Gogs: Simplico Git Service

暫無描述

class-wp-term-query.php 36KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. <?php
  2. /**
  3. * Taxonomy API: WP_Term_Query class.
  4. *
  5. * @package WordPress
  6. * @subpackage Taxonomy
  7. * @since 4.6.0
  8. */
  9. /**
  10. * Class used for querying terms.
  11. *
  12. * @since 4.6.0
  13. *
  14. * @see WP_Term_Query::__construct() for accepted arguments.
  15. */
  16. class WP_Term_Query {
  17. /**
  18. * SQL string used to perform database query.
  19. *
  20. * @since 4.6.0
  21. * @var string
  22. */
  23. public $request;
  24. /**
  25. * Metadata query container.
  26. *
  27. * @since 4.6.0
  28. * @var WP_Meta_Query A meta query instance.
  29. */
  30. public $meta_query = false;
  31. /**
  32. * Metadata query clauses.
  33. *
  34. * @since 4.6.0
  35. * @var array
  36. */
  37. protected $meta_query_clauses;
  38. /**
  39. * SQL query clauses.
  40. *
  41. * @since 4.6.0
  42. * @var array
  43. */
  44. protected $sql_clauses = array(
  45. 'select' => '',
  46. 'from' => '',
  47. 'where' => array(),
  48. 'orderby' => '',
  49. 'limits' => '',
  50. );
  51. /**
  52. * Query vars set by the user.
  53. *
  54. * @since 4.6.0
  55. * @var array
  56. */
  57. public $query_vars;
  58. /**
  59. * Default values for query vars.
  60. *
  61. * @since 4.6.0
  62. * @var array
  63. */
  64. public $query_var_defaults;
  65. /**
  66. * List of terms located by the query.
  67. *
  68. * @since 4.6.0
  69. * @var array
  70. */
  71. public $terms;
  72. /**
  73. * Constructor.
  74. *
  75. * Sets up the term query, based on the query vars passed.
  76. *
  77. * @since 4.6.0
  78. * @since 4.6.0 Introduced 'term_taxonomy_id' parameter.
  79. * @since 4.7.0 Introduced 'object_ids' parameter.
  80. * @since 4.9.0 Added 'slug__in' support for 'orderby'.
  81. *
  82. * @param string|array $query {
  83. * Optional. Array or query string of term query parameters. Default empty.
  84. *
  85. * @type string|array $taxonomy Taxonomy name, or array of taxonomies, to which results should
  86. * be limited.
  87. * @type int|int[] $object_ids Optional. Object ID, or array of object IDs. Results will be
  88. * limited to terms associated with these objects.
  89. * @type string $orderby Field(s) to order terms by. Accepts:
  90. * * term fields ('name', 'slug', 'term_group', 'term_id', 'id',
  91. * 'description', 'parent', 'term_order'). Unless `$object_ids`
  92. * is not empty, 'term_order' is treated the same as 'term_id'.
  93. * * 'count' to use the number of objects associated with the term.
  94. * * 'include' to match the 'order' of the $include param.
  95. * * 'slug__in' to match the 'order' of the $slug param.
  96. * * 'meta_value', 'meta_value_num'.
  97. * the value of `$meta_key`.
  98. * the array keys of `$meta_query`.
  99. * * 'none' to omit the ORDER BY clause.
  100. * Defaults to 'name'.
  101. * @type string $order Whether to order terms in ascending or descending order.
  102. * Accepts 'ASC' (ascending) or 'DESC' (descending).
  103. * Default 'ASC'.
  104. * @type bool|int $hide_empty Whether to hide terms not assigned to any posts. Accepts
  105. * 1|true or 0|false. Default 1|true.
  106. * @type int[]|string $include Array or comma/space-separated string of term IDs to include.
  107. * Default empty array.
  108. * @type int[]|string $exclude Array or comma/space-separated string of term IDs to exclude.
  109. * If $include is non-empty, $exclude is ignored.
  110. * Default empty array.
  111. * @type int[]|string $exclude_tree Array or comma/space-separated string of term IDs to exclude
  112. * along with all of their descendant terms. If $include is
  113. * non-empty, $exclude_tree is ignored. Default empty array.
  114. * @type int|string $number Maximum number of terms to return. Accepts ''|0 (all) or any
  115. * positive number. Default ''|0 (all). Note that $number may
  116. * not return accurate results when coupled with $object_ids.
  117. * See #41796 for details.
  118. * @type int $offset The number by which to offset the terms query. Default empty.
  119. * @type string $fields Term fields to query for. Accepts:
  120. * * 'all' Returns an array of complete term objects (`WP_Term[]`).
  121. * * 'all_with_object_id' Returns an array of term objects
  122. * with the 'object_id' param (`WP_Term[]`). Works only
  123. * when the `$object_ids` parameter is populated.
  124. * * 'ids' Returns an array of term IDs (`int[]`).
  125. * * 'tt_ids' Returns an array of term taxonomy IDs (`int[]`).
  126. * * 'names' Returns an array of term names (`string[]`).
  127. * * 'slugs' Returns an array of term slugs (`string[]`).
  128. * * 'count' Returns the number of matching terms (`int`).
  129. * * 'id=>parent' Returns an associative array of parent term IDs,
  130. * keyed by term ID (`int[]`).
  131. * * 'id=>name' Returns an associative array of term names,
  132. * keyed by term ID (`string[]`).
  133. * * 'id=>slug' Returns an associative array of term slugs,
  134. * keyed by term ID (`string[]`).
  135. * Default 'all'.
  136. * @type bool $count Whether to return a term count. If true, will take precedence
  137. * over `$fields`. Default false.
  138. * @type string|array $name Optional. Name or array of names to return term(s) for.
  139. * Default empty.
  140. * @type string|array $slug Optional. Slug or array of slugs to return term(s) for.
  141. * Default empty.
  142. * @type int|int[] $term_taxonomy_id Optional. Term taxonomy ID, or array of term taxonomy IDs,
  143. * to match when querying terms.
  144. * @type bool $hierarchical Whether to include terms that have non-empty descendants
  145. * (even if $hide_empty is set to true). Default true.
  146. * @type string $search Search criteria to match terms. Will be SQL-formatted with
  147. * wildcards before and after. Default empty.
  148. * @type string $name__like Retrieve terms with criteria by which a term is LIKE
  149. * `$name__like`. Default empty.
  150. * @type string $description__like Retrieve terms where the description is LIKE
  151. * `$description__like`. Default empty.
  152. * @type bool $pad_counts Whether to pad the quantity of a term's children in the
  153. * quantity of each term's "count" object variable.
  154. * Default false.
  155. * @type string $get Whether to return terms regardless of ancestry or whether the
  156. * terms are empty. Accepts 'all' or empty (disabled).
  157. * Default empty.
  158. * @type int $child_of Term ID to retrieve child terms of. If multiple taxonomies
  159. * are passed, $child_of is ignored. Default 0.
  160. * @type int|string $parent Parent term ID to retrieve direct-child terms of.
  161. * Default empty.
  162. * @type bool $childless True to limit results to terms that have no children.
  163. * This parameter has no effect on non-hierarchical taxonomies.
  164. * Default false.
  165. * @type string $cache_domain Unique cache key to be produced when this query is stored in
  166. * an object cache. Default is 'core'.
  167. * @type bool $update_term_meta_cache Whether to prime meta caches for matched terms. Default true.
  168. * @type array $meta_query Optional. Meta query clauses to limit retrieved terms by.
  169. * See `WP_Meta_Query`. Default empty.
  170. * @type string $meta_key Limit terms to those matching a specific metadata key.
  171. * Can be used in conjunction with `$meta_value`. Default empty.
  172. * @type string $meta_value Limit terms to those matching a specific metadata value.
  173. * Usually used in conjunction with `$meta_key`. Default empty.
  174. * @type string $meta_type MySQL data type that the `$meta_value` will be CAST to for
  175. * comparisons. Default empty.
  176. * @type string $meta_compare Comparison operator to test the 'meta_value'. Default empty.
  177. * }
  178. */
  179. public function __construct( $query = '' ) {
  180. $this->query_var_defaults = array(
  181. 'taxonomy' => null,
  182. 'object_ids' => null,
  183. 'orderby' => 'name',
  184. 'order' => 'ASC',
  185. 'hide_empty' => true,
  186. 'include' => array(),
  187. 'exclude' => array(),
  188. 'exclude_tree' => array(),
  189. 'number' => '',
  190. 'offset' => '',
  191. 'fields' => 'all',
  192. 'count' => false,
  193. 'name' => '',
  194. 'slug' => '',
  195. 'term_taxonomy_id' => '',
  196. 'hierarchical' => true,
  197. 'search' => '',
  198. 'name__like' => '',
  199. 'description__like' => '',
  200. 'pad_counts' => false,
  201. 'get' => '',
  202. 'child_of' => 0,
  203. 'parent' => '',
  204. 'childless' => false,
  205. 'cache_domain' => 'core',
  206. 'update_term_meta_cache' => true,
  207. 'meta_query' => '',
  208. 'meta_key' => '',
  209. 'meta_value' => '',
  210. 'meta_type' => '',
  211. 'meta_compare' => '',
  212. );
  213. if ( ! empty( $query ) ) {
  214. $this->query( $query );
  215. }
  216. }
  217. /**
  218. * Parse arguments passed to the term query with default query parameters.
  219. *
  220. * @since 4.6.0
  221. *
  222. * @param string|array $query WP_Term_Query arguments. See WP_Term_Query::__construct()
  223. */
  224. public function parse_query( $query = '' ) {
  225. if ( empty( $query ) ) {
  226. $query = $this->query_vars;
  227. }
  228. $taxonomies = isset( $query['taxonomy'] ) ? (array) $query['taxonomy'] : null;
  229. /**
  230. * Filters the terms query default arguments.
  231. *
  232. * Use {@see 'get_terms_args'} to filter the passed arguments.
  233. *
  234. * @since 4.4.0
  235. *
  236. * @param array $defaults An array of default get_terms() arguments.
  237. * @param string[] $taxonomies An array of taxonomy names.
  238. */
  239. $this->query_var_defaults = apply_filters( 'get_terms_defaults', $this->query_var_defaults, $taxonomies );
  240. $query = wp_parse_args( $query, $this->query_var_defaults );
  241. $query['number'] = absint( $query['number'] );
  242. $query['offset'] = absint( $query['offset'] );
  243. // 'parent' overrides 'child_of'.
  244. if ( 0 < (int) $query['parent'] ) {
  245. $query['child_of'] = false;
  246. }
  247. if ( 'all' === $query['get'] ) {
  248. $query['childless'] = false;
  249. $query['child_of'] = 0;
  250. $query['hide_empty'] = 0;
  251. $query['hierarchical'] = false;
  252. $query['pad_counts'] = false;
  253. }
  254. $query['taxonomy'] = $taxonomies;
  255. $this->query_vars = $query;
  256. /**
  257. * Fires after term query vars have been parsed.
  258. *
  259. * @since 4.6.0
  260. *
  261. * @param WP_Term_Query $this Current instance of WP_Term_Query.
  262. */
  263. do_action( 'parse_term_query', $this );
  264. }
  265. /**
  266. * Sets up the query and retrieves the results.
  267. *
  268. * The return type varies depending on the value passed to `$args['fields']`. See
  269. * WP_Term_Query::get_terms() for details.
  270. *
  271. * @since 4.6.0
  272. *
  273. * @param string|array $query Array or URL query string of parameters.
  274. * @return WP_Term[]|int[]|string[]|string Array of terms, or number of terms as numeric string
  275. * when 'count' is passed as a query var.
  276. */
  277. public function query( $query ) {
  278. $this->query_vars = wp_parse_args( $query );
  279. return $this->get_terms();
  280. }
  281. /**
  282. * Retrieves the query results.
  283. *
  284. * The return type varies depending on the value passed to `$args['fields']`.
  285. *
  286. * The following will result in an array of `WP_Term` objects being returned:
  287. *
  288. * - 'all'
  289. * - 'all_with_object_id'
  290. *
  291. * The following will result in a numeric string being returned:
  292. *
  293. * - 'count'
  294. *
  295. * The following will result in an array of text strings being returned:
  296. *
  297. * - 'id=>name'
  298. * - 'id=>slug'
  299. * - 'names'
  300. * - 'slugs'
  301. *
  302. * The following will result in an array of numeric strings being returned:
  303. *
  304. * - 'id=>parent'
  305. *
  306. * The following will result in an array of integers being returned:
  307. *
  308. * - 'ids'
  309. * - 'tt_ids'
  310. *
  311. * @since 4.6.0
  312. *
  313. * @global wpdb $wpdb WordPress database abstraction object.
  314. *
  315. * @return WP_Term[]|int[]|string[]|string Array of terms, or number of terms as numeric string
  316. * when 'count' is passed as a query var.
  317. */
  318. public function get_terms() {
  319. global $wpdb;
  320. $this->parse_query( $this->query_vars );
  321. $args = &$this->query_vars;
  322. // Set up meta_query so it's available to 'pre_get_terms'.
  323. $this->meta_query = new WP_Meta_Query();
  324. $this->meta_query->parse_query_vars( $args );
  325. /**
  326. * Fires before terms are retrieved.
  327. *
  328. * @since 4.6.0
  329. *
  330. * @param WP_Term_Query $this Current instance of WP_Term_Query (passed by reference).
  331. */
  332. do_action_ref_array( 'pre_get_terms', array( &$this ) );
  333. $taxonomies = (array) $args['taxonomy'];
  334. // Save queries by not crawling the tree in the case of multiple taxes or a flat tax.
  335. $has_hierarchical_tax = false;
  336. if ( $taxonomies ) {
  337. foreach ( $taxonomies as $_tax ) {
  338. if ( is_taxonomy_hierarchical( $_tax ) ) {
  339. $has_hierarchical_tax = true;
  340. }
  341. }
  342. } else {
  343. // When no taxonomies are provided, assume we have to descend the tree.
  344. $has_hierarchical_tax = true;
  345. }
  346. if ( ! $has_hierarchical_tax ) {
  347. $args['hierarchical'] = false;
  348. $args['pad_counts'] = false;
  349. }
  350. // 'parent' overrides 'child_of'.
  351. if ( 0 < (int) $args['parent'] ) {
  352. $args['child_of'] = false;
  353. }
  354. if ( 'all' === $args['get'] ) {
  355. $args['childless'] = false;
  356. $args['child_of'] = 0;
  357. $args['hide_empty'] = 0;
  358. $args['hierarchical'] = false;
  359. $args['pad_counts'] = false;
  360. }
  361. /**
  362. * Filters the terms query arguments.
  363. *
  364. * @since 3.1.0
  365. *
  366. * @param array $args An array of get_terms() arguments.
  367. * @param string[] $taxonomies An array of taxonomy names.
  368. */
  369. $args = apply_filters( 'get_terms_args', $args, $taxonomies );
  370. // Avoid the query if the queried parent/child_of term has no descendants.
  371. $child_of = $args['child_of'];
  372. $parent = $args['parent'];
  373. if ( $child_of ) {
  374. $_parent = $child_of;
  375. } elseif ( $parent ) {
  376. $_parent = $parent;
  377. } else {
  378. $_parent = false;
  379. }
  380. if ( $_parent ) {
  381. $in_hierarchy = false;
  382. foreach ( $taxonomies as $_tax ) {
  383. $hierarchy = _get_term_hierarchy( $_tax );
  384. if ( isset( $hierarchy[ $_parent ] ) ) {
  385. $in_hierarchy = true;
  386. }
  387. }
  388. if ( ! $in_hierarchy ) {
  389. if ( 'count' === $args['fields'] ) {
  390. return 0;
  391. } else {
  392. $this->terms = array();
  393. return $this->terms;
  394. }
  395. }
  396. }
  397. // 'term_order' is a legal sort order only when joining the relationship table.
  398. $_orderby = $this->query_vars['orderby'];
  399. if ( 'term_order' === $_orderby && empty( $this->query_vars['object_ids'] ) ) {
  400. $_orderby = 'term_id';
  401. }
  402. $orderby = $this->parse_orderby( $_orderby );
  403. if ( $orderby ) {
  404. $orderby = "ORDER BY $orderby";
  405. }
  406. $order = $this->parse_order( $this->query_vars['order'] );
  407. if ( $taxonomies ) {
  408. $this->sql_clauses['where']['taxonomy'] = "tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
  409. }
  410. $exclude = $args['exclude'];
  411. $exclude_tree = $args['exclude_tree'];
  412. $include = $args['include'];
  413. $inclusions = '';
  414. if ( ! empty( $include ) ) {
  415. $exclude = '';
  416. $exclude_tree = '';
  417. $inclusions = implode( ',', wp_parse_id_list( $include ) );
  418. }
  419. if ( ! empty( $inclusions ) ) {
  420. $this->sql_clauses['where']['inclusions'] = 't.term_id IN ( ' . $inclusions . ' )';
  421. }
  422. $exclusions = array();
  423. if ( ! empty( $exclude_tree ) ) {
  424. $exclude_tree = wp_parse_id_list( $exclude_tree );
  425. $excluded_children = $exclude_tree;
  426. foreach ( $exclude_tree as $extrunk ) {
  427. $excluded_children = array_merge(
  428. $excluded_children,
  429. (array) get_terms(
  430. array(
  431. 'taxonomy' => reset( $taxonomies ),
  432. 'child_of' => (int) $extrunk,
  433. 'fields' => 'ids',
  434. 'hide_empty' => 0,
  435. )
  436. )
  437. );
  438. }
  439. $exclusions = array_merge( $excluded_children, $exclusions );
  440. }
  441. if ( ! empty( $exclude ) ) {
  442. $exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
  443. }
  444. // 'childless' terms are those without an entry in the flattened term hierarchy.
  445. $childless = (bool) $args['childless'];
  446. if ( $childless ) {
  447. foreach ( $taxonomies as $_tax ) {
  448. $term_hierarchy = _get_term_hierarchy( $_tax );
  449. $exclusions = array_merge( array_keys( $term_hierarchy ), $exclusions );
  450. }
  451. }
  452. if ( ! empty( $exclusions ) ) {
  453. $exclusions = 't.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
  454. } else {
  455. $exclusions = '';
  456. }
  457. /**
  458. * Filters the terms to exclude from the terms query.
  459. *
  460. * @since 2.3.0
  461. *
  462. * @param string $exclusions `NOT IN` clause of the terms query.
  463. * @param array $args An array of terms query arguments.
  464. * @param string[] $taxonomies An array of taxonomy names.
  465. */
  466. $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies );
  467. if ( ! empty( $exclusions ) ) {
  468. // Must do string manipulation here for backward compatibility with filter.
  469. $this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions );
  470. }
  471. if (
  472. ( ! empty( $args['name'] ) ) ||
  473. ( is_string( $args['name'] ) && 0 !== strlen( $args['name'] ) )
  474. ) {
  475. $names = (array) $args['name'];
  476. foreach ( $names as &$_name ) {
  477. // `sanitize_term_field()` returns slashed data.
  478. $_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) );
  479. }
  480. $this->sql_clauses['where']['name'] = "t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
  481. }
  482. if (
  483. ( ! empty( $args['slug'] ) ) ||
  484. ( is_string( $args['slug'] ) && 0 !== strlen( $args['slug'] ) )
  485. ) {
  486. if ( is_array( $args['slug'] ) ) {
  487. $slug = array_map( 'sanitize_title', $args['slug'] );
  488. $this->sql_clauses['where']['slug'] = "t.slug IN ('" . implode( "', '", $slug ) . "')";
  489. } else {
  490. $slug = sanitize_title( $args['slug'] );
  491. $this->sql_clauses['where']['slug'] = "t.slug = '$slug'";
  492. }
  493. }
  494. if ( ! empty( $args['term_taxonomy_id'] ) ) {
  495. if ( is_array( $args['term_taxonomy_id'] ) ) {
  496. $tt_ids = implode( ',', array_map( 'intval', $args['term_taxonomy_id'] ) );
  497. $this->sql_clauses['where']['term_taxonomy_id'] = "tt.term_taxonomy_id IN ({$tt_ids})";
  498. } else {
  499. $this->sql_clauses['where']['term_taxonomy_id'] = $wpdb->prepare( 'tt.term_taxonomy_id = %d', $args['term_taxonomy_id'] );
  500. }
  501. }
  502. if ( ! empty( $args['name__like'] ) ) {
  503. $this->sql_clauses['where']['name__like'] = $wpdb->prepare( 't.name LIKE %s', '%' . $wpdb->esc_like( $args['name__like'] ) . '%' );
  504. }
  505. if ( ! empty( $args['description__like'] ) ) {
  506. $this->sql_clauses['where']['description__like'] = $wpdb->prepare( 'tt.description LIKE %s', '%' . $wpdb->esc_like( $args['description__like'] ) . '%' );
  507. }
  508. if ( ! empty( $args['object_ids'] ) ) {
  509. $object_ids = $args['object_ids'];
  510. if ( ! is_array( $object_ids ) ) {
  511. $object_ids = array( $object_ids );
  512. }
  513. $object_ids = implode( ', ', array_map( 'intval', $object_ids ) );
  514. $this->sql_clauses['where']['object_ids'] = "tr.object_id IN ($object_ids)";
  515. }
  516. /*
  517. * When querying for object relationships, the 'count > 0' check
  518. * added by 'hide_empty' is superfluous.
  519. */
  520. if ( ! empty( $args['object_ids'] ) ) {
  521. $args['hide_empty'] = false;
  522. }
  523. if ( '' !== $parent ) {
  524. $parent = (int) $parent;
  525. $this->sql_clauses['where']['parent'] = "tt.parent = '$parent'";
  526. }
  527. $hierarchical = $args['hierarchical'];
  528. if ( 'count' === $args['fields'] ) {
  529. $hierarchical = false;
  530. }
  531. if ( $args['hide_empty'] && ! $hierarchical ) {
  532. $this->sql_clauses['where']['count'] = 'tt.count > 0';
  533. }
  534. $number = $args['number'];
  535. $offset = $args['offset'];
  536. // Don't limit the query results when we have to descend the family tree.
  537. if ( $number && ! $hierarchical && ! $child_of && '' === $parent ) {
  538. if ( $offset ) {
  539. $limits = 'LIMIT ' . $offset . ',' . $number;
  540. } else {
  541. $limits = 'LIMIT ' . $number;
  542. }
  543. } else {
  544. $limits = '';
  545. }
  546. if ( ! empty( $args['search'] ) ) {
  547. $this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] );
  548. }
  549. // Meta query support.
  550. $join = '';
  551. $distinct = '';
  552. // Reparse meta_query query_vars, in case they were modified in a 'pre_get_terms' callback.
  553. $this->meta_query->parse_query_vars( $this->query_vars );
  554. $mq_sql = $this->meta_query->get_sql( 'term', 't', 'term_id' );
  555. $meta_clauses = $this->meta_query->get_clauses();
  556. if ( ! empty( $meta_clauses ) ) {
  557. $join .= $mq_sql['join'];
  558. $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] );
  559. $distinct .= 'DISTINCT';
  560. }
  561. $selects = array();
  562. switch ( $args['fields'] ) {
  563. case 'all':
  564. case 'all_with_object_id':
  565. case 'tt_ids':
  566. case 'slugs':
  567. $selects = array( 't.*', 'tt.*' );
  568. if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
  569. $selects[] = 'tr.object_id';
  570. }
  571. break;
  572. case 'ids':
  573. case 'id=>parent':
  574. $selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
  575. break;
  576. case 'names':
  577. $selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
  578. break;
  579. case 'count':
  580. $orderby = '';
  581. $order = '';
  582. $selects = array( 'COUNT(*)' );
  583. break;
  584. case 'id=>name':
  585. $selects = array( 't.term_id', 't.name', 'tt.parent', 'tt.count', 'tt.taxonomy' );
  586. break;
  587. case 'id=>slug':
  588. $selects = array( 't.term_id', 't.slug', 'tt.parent', 'tt.count', 'tt.taxonomy' );
  589. break;
  590. }
  591. $_fields = $args['fields'];
  592. /**
  593. * Filters the fields to select in the terms query.
  594. *
  595. * Field lists modified using this filter will only modify the term fields returned
  596. * by the function when the `$fields` parameter set to 'count' or 'all'. In all other
  597. * cases, the term fields in the results array will be determined by the `$fields`
  598. * parameter alone.
  599. *
  600. * Use of this filter can result in unpredictable behavior, and is not recommended.
  601. *
  602. * @since 2.8.0
  603. *
  604. * @param string[] $selects An array of fields to select for the terms query.
  605. * @param array $args An array of term query arguments.
  606. * @param string[] $taxonomies An array of taxonomy names.
  607. */
  608. $fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
  609. $join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
  610. if ( ! empty( $this->query_vars['object_ids'] ) ) {
  611. $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
  612. }
  613. $where = implode( ' AND ', $this->sql_clauses['where'] );
  614. /**
  615. * Filters the terms query SQL clauses.
  616. *
  617. * @since 3.1.0
  618. *
  619. * @param string[] $pieces Array of query SQL clauses.
  620. * @param string[] $taxonomies An array of taxonomy names.
  621. * @param array $args An array of term query arguments.
  622. */
  623. $clauses = apply_filters( 'terms_clauses', compact( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ), $taxonomies, $args );
  624. $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
  625. $join = isset( $clauses['join'] ) ? $clauses['join'] : '';
  626. $where = isset( $clauses['where'] ) ? $clauses['where'] : '';
  627. $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
  628. $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
  629. $order = isset( $clauses['order'] ) ? $clauses['order'] : '';
  630. $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
  631. if ( $where ) {
  632. $where = "WHERE $where";
  633. }
  634. $this->sql_clauses['select'] = "SELECT $distinct $fields";
  635. $this->sql_clauses['from'] = "FROM $wpdb->terms AS t $join";
  636. $this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : '';
  637. $this->sql_clauses['limits'] = $limits;
  638. $this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
  639. $this->terms = null;
  640. /**
  641. * Filters the terms array before the query takes place.
  642. *
  643. * Return a non-null value to bypass WordPress' default term queries.
  644. *
  645. * @since 5.3.0
  646. *
  647. * @param array|null $terms Return an array of term data to short-circuit WP's term query,
  648. * or null to allow WP queries to run normally.
  649. * @param WP_Term_Query $query The WP_Term_Query instance, passed by reference.
  650. */
  651. $this->terms = apply_filters_ref_array( 'terms_pre_query', array( $this->terms, &$this ) );
  652. if ( null !== $this->terms ) {
  653. return $this->terms;
  654. }
  655. // $args can be anything. Only use the args defined in defaults to compute the key.
  656. $key = md5( serialize( wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ) ) . serialize( $taxonomies ) . $this->request );
  657. $last_changed = wp_cache_get_last_changed( 'terms' );
  658. $cache_key = "get_terms:$key:$last_changed";
  659. $cache = wp_cache_get( $cache_key, 'terms' );
  660. if ( false !== $cache ) {
  661. if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
  662. $cache = $this->populate_terms( $cache );
  663. }
  664. $this->terms = $cache;
  665. return $this->terms;
  666. }
  667. if ( 'count' === $_fields ) {
  668. $count = $wpdb->get_var( $this->request );
  669. wp_cache_set( $cache_key, $count, 'terms' );
  670. return $count;
  671. }
  672. $terms = $wpdb->get_results( $this->request );
  673. if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
  674. update_term_cache( $terms );
  675. }
  676. // Prime termmeta cache.
  677. if ( $args['update_term_meta_cache'] ) {
  678. $term_ids = wp_list_pluck( $terms, 'term_id' );
  679. update_termmeta_cache( $term_ids );
  680. }
  681. if ( empty( $terms ) ) {
  682. wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
  683. return array();
  684. }
  685. if ( $child_of ) {
  686. foreach ( $taxonomies as $_tax ) {
  687. $children = _get_term_hierarchy( $_tax );
  688. if ( ! empty( $children ) ) {
  689. $terms = _get_term_children( $child_of, $terms, $_tax );
  690. }
  691. }
  692. }
  693. // Update term counts to include children.
  694. if ( $args['pad_counts'] && 'all' === $_fields ) {
  695. foreach ( $taxonomies as $_tax ) {
  696. _pad_term_counts( $terms, $_tax );
  697. }
  698. }
  699. // Make sure we show empty categories that have children.
  700. if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
  701. foreach ( $terms as $k => $term ) {
  702. if ( ! $term->count ) {
  703. $children = get_term_children( $term->term_id, $term->taxonomy );
  704. if ( is_array( $children ) ) {
  705. foreach ( $children as $child_id ) {
  706. $child = get_term( $child_id, $term->taxonomy );
  707. if ( $child->count ) {
  708. continue 2;
  709. }
  710. }
  711. }
  712. // It really is empty.
  713. unset( $terms[ $k ] );
  714. }
  715. }
  716. }
  717. /*
  718. * When querying for terms connected to objects, we may get
  719. * duplicate results. The duplicates should be preserved if
  720. * `$fields` is 'all_with_object_id', but should otherwise be
  721. * removed.
  722. */
  723. if ( ! empty( $args['object_ids'] ) && 'all_with_object_id' !== $_fields ) {
  724. $_tt_ids = array();
  725. $_terms = array();
  726. foreach ( $terms as $term ) {
  727. if ( isset( $_tt_ids[ $term->term_id ] ) ) {
  728. continue;
  729. }
  730. $_tt_ids[ $term->term_id ] = 1;
  731. $_terms[] = $term;
  732. }
  733. $terms = $_terms;
  734. }
  735. $_terms = array();
  736. if ( 'id=>parent' === $_fields ) {
  737. foreach ( $terms as $term ) {
  738. $_terms[ $term->term_id ] = $term->parent;
  739. }
  740. } elseif ( 'ids' === $_fields ) {
  741. foreach ( $terms as $term ) {
  742. $_terms[] = (int) $term->term_id;
  743. }
  744. } elseif ( 'tt_ids' === $_fields ) {
  745. foreach ( $terms as $term ) {
  746. $_terms[] = (int) $term->term_taxonomy_id;
  747. }
  748. } elseif ( 'names' === $_fields ) {
  749. foreach ( $terms as $term ) {
  750. $_terms[] = $term->name;
  751. }
  752. } elseif ( 'slugs' === $_fields ) {
  753. foreach ( $terms as $term ) {
  754. $_terms[] = $term->slug;
  755. }
  756. } elseif ( 'id=>name' === $_fields ) {
  757. foreach ( $terms as $term ) {
  758. $_terms[ $term->term_id ] = $term->name;
  759. }
  760. } elseif ( 'id=>slug' === $_fields ) {
  761. foreach ( $terms as $term ) {
  762. $_terms[ $term->term_id ] = $term->slug;
  763. }
  764. }
  765. if ( ! empty( $_terms ) ) {
  766. $terms = $_terms;
  767. }
  768. // Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
  769. if ( $hierarchical && $number && is_array( $terms ) ) {
  770. if ( $offset >= count( $terms ) ) {
  771. $terms = array();
  772. } else {
  773. $terms = array_slice( $terms, $offset, $number, true );
  774. }
  775. }
  776. wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
  777. if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
  778. $terms = $this->populate_terms( $terms );
  779. }
  780. $this->terms = $terms;
  781. return $this->terms;
  782. }
  783. /**
  784. * Parse and sanitize 'orderby' keys passed to the term query.
  785. *
  786. * @since 4.6.0
  787. *
  788. * @global wpdb $wpdb WordPress database abstraction object.
  789. *
  790. * @param string $orderby_raw Alias for the field to order by.
  791. * @return string|false Value to used in the ORDER clause. False otherwise.
  792. */
  793. protected function parse_orderby( $orderby_raw ) {
  794. $_orderby = strtolower( $orderby_raw );
  795. $maybe_orderby_meta = false;
  796. if ( in_array( $_orderby, array( 'term_id', 'name', 'slug', 'term_group' ), true ) ) {
  797. $orderby = "t.$_orderby";
  798. } elseif ( in_array( $_orderby, array( 'count', 'parent', 'taxonomy', 'term_taxonomy_id', 'description' ), true ) ) {
  799. $orderby = "tt.$_orderby";
  800. } elseif ( 'term_order' === $_orderby ) {
  801. $orderby = 'tr.term_order';
  802. } elseif ( 'include' === $_orderby && ! empty( $this->query_vars['include'] ) ) {
  803. $include = implode( ',', wp_parse_id_list( $this->query_vars['include'] ) );
  804. $orderby = "FIELD( t.term_id, $include )";
  805. } elseif ( 'slug__in' === $_orderby && ! empty( $this->query_vars['slug'] ) && is_array( $this->query_vars['slug'] ) ) {
  806. $slugs = implode( "', '", array_map( 'sanitize_title_for_query', $this->query_vars['slug'] ) );
  807. $orderby = "FIELD( t.slug, '" . $slugs . "')";
  808. } elseif ( 'none' === $_orderby ) {
  809. $orderby = '';
  810. } elseif ( empty( $_orderby ) || 'id' === $_orderby || 'term_id' === $_orderby ) {
  811. $orderby = 't.term_id';
  812. } else {
  813. $orderby = 't.name';
  814. // This may be a value of orderby related to meta.
  815. $maybe_orderby_meta = true;
  816. }
  817. /**
  818. * Filters the ORDERBY clause of the terms query.
  819. *
  820. * @since 2.8.0
  821. *
  822. * @param string $orderby `ORDERBY` clause of the terms query.
  823. * @param array $args An array of term query arguments.
  824. * @param string[] $taxonomies An array of taxonomy names.
  825. */
  826. $orderby = apply_filters( 'get_terms_orderby', $orderby, $this->query_vars, $this->query_vars['taxonomy'] );
  827. // Run after the 'get_terms_orderby' filter for backward compatibility.
  828. if ( $maybe_orderby_meta ) {
  829. $maybe_orderby_meta = $this->parse_orderby_meta( $_orderby );
  830. if ( $maybe_orderby_meta ) {
  831. $orderby = $maybe_orderby_meta;
  832. }
  833. }
  834. return $orderby;
  835. }
  836. /**
  837. * Generate the ORDER BY clause for an 'orderby' param that is potentially related to a meta query.
  838. *
  839. * @since 4.6.0
  840. *
  841. * @param string $orderby_raw Raw 'orderby' value passed to WP_Term_Query.
  842. * @return string ORDER BY clause.
  843. */
  844. protected function parse_orderby_meta( $orderby_raw ) {
  845. $orderby = '';
  846. // Tell the meta query to generate its SQL, so we have access to table aliases.
  847. $this->meta_query->get_sql( 'term', 't', 'term_id' );
  848. $meta_clauses = $this->meta_query->get_clauses();
  849. if ( ! $meta_clauses || ! $orderby_raw ) {
  850. return $orderby;
  851. }
  852. $allowed_keys = array();
  853. $primary_meta_key = null;
  854. $primary_meta_query = reset( $meta_clauses );
  855. if ( ! empty( $primary_meta_query['key'] ) ) {
  856. $primary_meta_key = $primary_meta_query['key'];
  857. $allowed_keys[] = $primary_meta_key;
  858. }
  859. $allowed_keys[] = 'meta_value';
  860. $allowed_keys[] = 'meta_value_num';
  861. $allowed_keys = array_merge( $allowed_keys, array_keys( $meta_clauses ) );
  862. if ( ! in_array( $orderby_raw, $allowed_keys, true ) ) {
  863. return $orderby;
  864. }
  865. switch ( $orderby_raw ) {
  866. case $primary_meta_key:
  867. case 'meta_value':
  868. if ( ! empty( $primary_meta_query['type'] ) ) {
  869. $orderby = "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
  870. } else {
  871. $orderby = "{$primary_meta_query['alias']}.meta_value";
  872. }
  873. break;
  874. case 'meta_value_num':
  875. $orderby = "{$primary_meta_query['alias']}.meta_value+0";
  876. break;
  877. default:
  878. if ( array_key_exists( $orderby_raw, $meta_clauses ) ) {
  879. // $orderby corresponds to a meta_query clause.
  880. $meta_clause = $meta_clauses[ $orderby_raw ];
  881. $orderby = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
  882. }
  883. break;
  884. }
  885. return $orderby;
  886. }
  887. /**
  888. * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
  889. *
  890. * @since 4.6.0
  891. *
  892. * @param string $order The 'order' query variable.
  893. * @return string The sanitized 'order' query variable.
  894. */
  895. protected function parse_order( $order ) {
  896. if ( ! is_string( $order ) || empty( $order ) ) {
  897. return 'DESC';
  898. }
  899. if ( 'ASC' === strtoupper( $order ) ) {
  900. return 'ASC';
  901. } else {
  902. return 'DESC';
  903. }
  904. }
  905. /**
  906. * Used internally to generate a SQL string related to the 'search' parameter.
  907. *
  908. * @since 4.6.0
  909. *
  910. * @global wpdb $wpdb WordPress database abstraction object.
  911. *
  912. * @param string $string
  913. * @return string
  914. */
  915. protected function get_search_sql( $string ) {
  916. global $wpdb;
  917. $like = '%' . $wpdb->esc_like( $string ) . '%';
  918. return $wpdb->prepare( '((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
  919. }
  920. /**
  921. * Creates an array of term objects from an array of term IDs.
  922. *
  923. * Also discards invalid term objects.
  924. *
  925. * @since 4.9.8
  926. *
  927. * @param array $term_ids Term IDs.
  928. * @return array
  929. */
  930. protected function populate_terms( $term_ids ) {
  931. $terms = array();
  932. if ( ! is_array( $term_ids ) ) {
  933. return $terms;
  934. }
  935. foreach ( $term_ids as $key => $term_id ) {
  936. $term = get_term( $term_id );
  937. if ( $term instanceof WP_Term ) {
  938. $terms[ $key ] = $term;
  939. }
  940. }
  941. return $terms;
  942. }
  943. }